Android Fastboot源码分析
在Android的SDK中自带了fastboot,路径为
${SDK_HOME}/platform-tools/fastboot
查看help:
usage: fastboot [
够长的,好多参数解释看得也不是很懂。
自己编译Android源码也会产生fastboot,路径为:
${OUT}/host/darwin-x86/bin/fastboot //Mac的编译结果在darwin-x86下
查看help:
usage: fastboot [
两者还不太一样,好像自己编译的在功能上是SDK自带的子集。在源码中有fastboot相关的代码,正好研究一下。
情景一
我们使用fastboot的第一个有效命令(不算 fastboot -h)通常是fastboot devices
,我们来跟踪一下:
if (argc > 0 && !strcmp(*argv, "devices")) { skip(1); list_devices(); return 0; }
list_devices:
void list_devices(void) { // We don't actually open a USB device here, // just getting our callback called so we can // list all the connected devices. usb_open(list_devices_callback);}
usb_open,不同操作系统有不同的实现,为了方便理解,我们分析Linux版本的实现(system/core/fastboot/usb_linux.c):
usb_handle *usb_open(ifc_match_func callback){ return find_usb_device("/dev/bus/usb", callback);}
find_usb_device:
static usb_handle *find_usb_device(const char *base, ifc_match_func callback){ usb_handle *usb = 0; char busname[64], devname[64]; char desc[1024]; int n, in, out, ifc; DIR *busdir, *devdir; struct dirent *de; int fd; int writable; busdir = opendir(base); if(busdir == 0) return 0; while((de = readdir(busdir)) && (usb == 0)) { if(badname(de->d_name)) continue; sprintf(busname, "%s/%s", base, de->d_name); devdir = opendir(busname); if(devdir == 0) continue;// DBG("[ scanning %s ]\n", busname); while((de = readdir(devdir)) && (usb == 0)) { if(badname(de->d_name)) continue; sprintf(devname, "%s/%s", busname, de->d_name);// DBG("[ scanning %s ]\n", devname); writable = 1; if((fd = open(devname, O_RDWR)) < 0) { // Check if we have read-only access, so we can give a helpful // diagnostic like "adb devices" does. writable = 0; if((fd = open(devname, O_RDONLY)) < 0) { continue; } } n = read(fd, desc, sizeof(desc)); if(filter_usb_device(fd, desc, n, writable, callback, &in, &out, &ifc) == 0) { usb = calloc(1, sizeof(usb_handle)); strcpy(usb->fname, devname); usb->ep_in = in; usb->ep_out = out; usb->desc = fd; n = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifc); if(n != 0) { close(fd); free(usb); usb = 0; continue; } } else { close(fd); } } closedir(devdir); } closedir(busdir); return usb;}
循环读取/dev/bus/usb
目录下及子目录中的信息,解析并使用filter_usb_device
过滤,然后对fastboot模式的usb进行callback调用,至于如何过滤fastboot模式的usb,这里面涉及到usb相关的知识,我也不是很了解,应该是通过usb信息中的某个标识来识别的,屌大的同学可以给我讲讲。
对callback的调用:
if(callback(&info) == 0) { *ept_in_id = in; *ept_out_id = out; *ifc_id = ifc->bInterfaceNumber; return 0; }
回到list_devices_callback
:
int list_devices_callback(usb_ifc_info *info){ if (match_fastboot_with_serial(info, NULL) == 0) { char* serial = info->serial_number; if (!info->writable) { serial = "no permissions"; // like "adb devices" } if (!serial[0]) { serial = "????????????"; } // output compatible with "adb devices" if (!long_listing) { printf("%s\tfastboot\n", serial); } else if (!info->device_path) { printf("%-22s fastboot\n", serial); } else { printf("%-22s fastboot %s\n", serial, info->device_path); } } return -1;}
其实就是输出连接的设备信息,假如是long_listing,会把device_path也输出来,long_listing通过 -l
指定:
$ fastboot devices -l01d977445292ca8c fastboot usb:337838080X
情景二
在刷机的时候,通常使用fastboot -w flashall
,先看看-w
:
case 'w': wants_wipe = 1; break;...if (wants_wipe) { fb_queue_erase("userdata"); fb_queue_format("userdata", 1); fb_queue_erase("cache"); fb_queue_format("cache", 1); }
带上这个选项,会清除userdata
和cache
中的内容。
再看看flashall
:
else if(!strcmp(*argv, "flashall")) { skip(1); do_flashall(usb, erase_first); wants_reboot = 1; }
do_flashall:
void do_flashall(usb_handle *usb, int erase_first){ char *fname; void *data; unsigned sz; struct fastboot_buffer buf; int i; queue_info_dump(); fb_queue_query_save("product", cur_product, sizeof(cur_product)); fname = find_item("info", product); if (fname == 0) die("cannot find android-info.txt"); data = load_file(fname, &sz); if (data == 0) die("could not load android-info.txt: %s", strerror(errno)); setup_requirements(data, sz); for (i = 0; i < ARRAY_SIZE(images); i++) { fname = find_item(images[i].part_name, product); if (load_buf(usb, fname, &buf)) { if (images[i].is_optional) continue; die("could not load %s\n", images[i].img_name); } do_send_signature(fname); if (erase_first && needs_erase(images[i].part_name)) { fb_queue_erase(images[i].part_name); } flash_buf(images[i].part_name, &buf); }}
find_item:
char *find_item(const char *item, const char *product){ char *dir; char *fn; char path[PATH_MAX + 128]; if(!strcmp(item,"boot")) { fn = "boot.img"; } else if(!strcmp(item,"recovery")) { fn = "recovery.img"; } else if(!strcmp(item,"system")) { fn = "system.img"; } else if(!strcmp(item,"userdata")) { fn = "userdata.img"; } else if(!strcmp(item,"cache")) { fn = "cache.img"; } else if(!strcmp(item,"info")) { fn = "android-info.txt"; } else { fprintf(stderr,"unknown partition '%s'\n", item); return 0; } if(product) { get_my_path(path); sprintf(path + strlen(path), "../../../target/product/%s/%s", product, fn); return strdup(path); } dir = getenv("ANDROID_PRODUCT_OUT"); if((dir == 0) || (dir[0] == 0)) { die("neither -p product specified nor ANDROID_PRODUCT_OUT set"); return 0; } sprintf(path, "%s/%s", dir, fn); return strdup(path);}
会根据环境变量ANDROID_PRODUCT_OUT
指定的目录下去找相关的文件,这里是android-info.txt
。我们在刷机的时候,如果报找不到img文件时,需要设置ANDROID_PRODUCT_OUT就是这个原因。
接下来就是找相关的img文件,然后刷:
static struct { char img_name[13]; char sig_name[13]; char part_name[9]; bool is_optional;} images[3] = { {"boot.img", "boot.sig", "boot", false}, {"recovery.img", "recovery.sig", "recovery", true}, {"system.img", "system.sig", "system", false},};...for (i = 0; i < ARRAY_SIZE(images); i++) { fd = unzip_to_file(zip, images[i].img_name); if (fd < 0) { if (images[i].is_optional) continue; die("update package missing %s", images[i].img_name); } rc = load_buf_fd(usb, fd, &buf); if (rc) die("cannot load %s from flash", images[i].img_name); do_update_signature(zip, images[i].sig_name); if (erase_first && needs_erase(images[i].part_name)) { fb_queue_erase(images[i].part_name); } flash_buf(images[i].part_name, &buf); /* not closing the fd here since the sparse code keeps the fd around * but hasn't mmaped data yet. The tmpfile will get cleaned up when the * program exits. */ }
可以看到,flashall的时候,会批量刷入boot.img、recovery.img、system.img,其中recovery.img是可选刷入的。
其中load_buf_fd
将解压的文件load到buf中。
flash_buf:
static void flash_buf(const char *pname, struct fastboot_buffer *buf){ struct sparse_file **s; switch (buf->type) { case FB_BUFFER_SPARSE: s = buf->data; while (*s) { int64_t sz64 = sparse_file_len(*s, true, false); fb_queue_flash_sparse(pname, *s++, sz64); } break; case FB_BUFFER: fb_queue_flash(pname, buf->data, buf->sz); break; default: die("unknown buffer type: %d", buf->type); }}
fb_queue_flash和fb_queue_flash_sparse:
void fb_queue_flash(const char *ptn, void *data, unsigned sz){ Action *a; a = queue_action(OP_DOWNLOAD, ""); a->data = data; a->size = sz; a->msg = mkmsg("sending '%s' (%d KB)", ptn, sz / 1024); a = queue_action(OP_COMMAND, "flash:%s", ptn); a->msg = mkmsg("writing '%s'", ptn);}void fb_queue_flash_sparse(const char *ptn, struct sparse_file *s, unsigned sz){ Action *a; a = queue_action(OP_DOWNLOAD_SPARSE, ""); a->data = s; a->size = 0; a->msg = mkmsg("sending sparse '%s' (%d KB)", ptn, sz / 1024); a = queue_action(OP_COMMAND, "flash:%s", ptn); a->msg = mkmsg("writing '%s'", ptn);}
queue_action:
static Action *queue_action(unsigned op, const char *fmt, ...){ Action *a; va_list ap; size_t cmdsize; a = calloc(1, sizeof(Action)); if (a == 0) die("out of memory"); va_start(ap, fmt); cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap); va_end(ap); if (cmdsize >= sizeof(a->cmd)) { free(a); die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd)); } if (action_last) { action_last->next = a; } else { action_list = a; } action_last = a; a->op = op; a->func = cb_default; a->start = -1; return a;}
其实就是将对应命令和数据放入到action_last
链表中。那肯定有另外一个地方对这个链表进行真正的操作。
fb_execute_queue:
int fb_execute_queue(usb_handle *usb){ Action *a; char resp[FB_RESPONSE_SZ+1]; int status = 0; a = action_list; if (!a) return status; resp[FB_RESPONSE_SZ] = 0; double start = -1; for (a = action_list; a; a = a->next) { a->start = now(); if (start < 0) start = a->start; if (a->msg) { // fprintf(stderr,"%30s... ",a->msg); fprintf(stderr,"%s...\n",a->msg); } if (a->op == OP_DOWNLOAD) { status = fb_download_data(usb, a->data, a->size); status = a->func(a, status, status ? fb_get_error() : ""); if (status) break; } else if (a->op == OP_COMMAND) { status = fb_command(usb, a->cmd); status = a->func(a, status, status ? fb_get_error() : ""); if (status) break; } else if (a->op == OP_QUERY) { status = fb_command_response(usb, a->cmd, resp); status = a->func(a, status, status ? fb_get_error() : resp); if (status) break; } else if (a->op == OP_NOTICE) { fprintf(stderr,"%s\n",(char*)a->data); } else if (a->op == OP_FORMAT) { status = fb_format(a, usb, (int)a->data); status = a->func(a, status, status ? fb_get_error() : ""); if (status) break; } else if (a->op == OP_DOWNLOAD_SPARSE) { status = fb_download_data_sparse(usb, a->data); status = a->func(a, status, status ? fb_get_error() : ""); if (status) break; } else { die("bogus action"); } } fprintf(stderr,"finished. total time: %.3fs\n", (now() - start)); return status;}
fb_download_data:
int fb_download_data(usb_handle *usb, const void *data, unsigned size){ char cmd[64]; int r; sprintf(cmd, "download:%08x", size); r = _command_send(usb, cmd, data, size, 0); if(r < 0) { return -1; } else { return 0; }}
_command_send:
static int _command_send(usb_handle *usb, const char *cmd, const void *data, unsigned size, char *response){ int r; if (size == 0) { return -1; } r = _command_start(usb, cmd, size, response); if (r < 0) { return -1; } r = _command_data(usb, data, size); if (r < 0) { return -1; } r = _command_end(usb); if(r < 0) { return -1; } return size;}
_command_start:
static int _command_start(usb_handle *usb, const char *cmd, unsigned size, char *response){ int cmdsize = strlen(cmd); int r; if(response) { response[0] = 0; } if(cmdsize > 64) { sprintf(ERROR,"command too large"); return -1; } if(usb_write(usb, cmd, cmdsize) != cmdsize) { sprintf(ERROR,"command write failed (%s)", strerror(errno)); usb_close(usb); return -1; } return check_response(usb, size, response);}
usb_write(system/core/fastboot/usb_linux.c):
int usb_write(usb_handle *h, const void *_data, int len){ unsigned char *data = (unsigned char*) _data; unsigned count = 0; struct usbdevfs_bulktransfer bulk; int n; if(h->ep_out == 0) { return -1; } if(len == 0) { bulk.ep = h->ep_out; bulk.len = 0; bulk.data = data; bulk.timeout = 0; n = ioctl(h->desc, USBDEVFS_BULK, &bulk); if(n != 0) { fprintf(stderr,"ERROR: n = %d, errno = %d (%s)\n", n, errno, strerror(errno)); return -1; } return 0; } while(len > 0) { int xfer; xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len; bulk.ep = h->ep_out; bulk.len = xfer; bulk.data = data; bulk.timeout = 0; n = ioctl(h->desc, USBDEVFS_BULK, &bulk); if(n != xfer) { DBG("ERROR: n = %d, errno = %d (%s)\n", n, errno, strerror(errno)); return -1; } count += xfer; len -= xfer; data += xfer; } return count;}
这里就是将内容写入到usb_handle
对应的usb设备中。这样我们的数据就通过usb写入到设备中了,而在设备中,有对应的usb驱动程序来处理写过去的数据。对于设备如何处理写过去的数据,我并没有搜索到相关的代码,也许在厂商的驱动程序里面,同样,屌大的同学可以给我讲讲。
情景三
有时候我们只刷单个的img,使用的命令是fastboot flash xxx xxx.img
,也来看看吧:
else if(!strcmp(*argv, "flash")) { char *pname = argv[1]; char *fname = 0; require(2); if (argc > 2) { fname = argv[2]; skip(3); } else { fname = find_item(pname, product); skip(2); } if (fname == 0) die("cannot determine image filename for '%s'", pname); if (erase_first && needs_erase(pname)) { fb_queue_erase(pname); } do_flash(usb, pname, fname); }
do_flash:
void do_flash(usb_handle *usb, const char *pname, const char *fname){ struct fastboot_buffer buf; if (load_buf(usb, fname, &buf)) { die("cannot load '%s'", fname); } flash_buf(pname, &buf);}
同样的是将数据load到buf,然后调用flash_buf
,flash_buf
前面已经分析过了,就不再复述。
情景四:
在help中,我们看到这个:
boot [ ] download and boot kernel
貌似对boot镜像有特别的处理,跟踪一下:
else if(!strcmp(*argv, "boot")) { char *kname = 0; char *rname = 0; skip(1); if (argc > 0) { kname = argv[0]; skip(1); } if (argc > 0) { rname = argv[0]; skip(1); } data = load_bootable_image(kname, rname, &sz, cmdline); if (data == 0) return 1; fb_queue_download("boot.img", data, sz); fb_queue_command("boot", "booting"); }
其中,第一个参数为kernel文件名,第二个参数为ramdisk文件名(可选的)。
load_bootable_image:
void *load_bootable_image(const char *kernel, const char *ramdisk, unsigned *sz, const char *cmdline){ void *kdata = 0, *rdata = 0; unsigned ksize = 0, rsize = 0; void *bdata; unsigned bsize; if(kernel == 0) { fprintf(stderr, "no image specified\n"); return 0; } kdata = load_file(kernel, &ksize); if(kdata == 0) { fprintf(stderr, "cannot load '%s': %s\n", kernel, strerror(errno)); return 0; } /* is this actually a boot image? */ if(!memcmp(kdata, BOOT_MAGIC, BOOT_MAGIC_SIZE)) { if(cmdline) bootimg_set_cmdline((boot_img_hdr*) kdata, cmdline); if(ramdisk) { fprintf(stderr, "cannot boot a boot.img *and* ramdisk\n"); return 0; } *sz = ksize; return kdata; } if(ramdisk) { rdata = load_file(ramdisk, &rsize); if(rdata == 0) { fprintf(stderr,"cannot load '%s': %s\n", ramdisk, strerror(errno)); return 0; } } fprintf(stderr,"creating boot image...\n"); bdata = mkbootimg(kdata, ksize, kernel_offset, rdata, rsize, ramdisk_offset, 0, 0, second_offset, page_size, base_addr, tags_offset, &bsize); if(bdata == 0) { fprintf(stderr,"failed to create boot.img\n"); return 0; } if(cmdline) bootimg_set_cmdline((boot_img_hdr*) bdata, cmdline); fprintf(stderr,"creating boot image - %d bytes\n", bsize); *sz = bsize; return bdata;}
先通过load_file
将kernel加载到内存中,如果发现其魔数是ANDROID!
,代表其已经是打包好的boot.img文件(包含了kernel和ramdisk),这种情况下就忽略对ramdisk的处理。
不然的话,就将ramdisk也加载到内存,并使用mkbootimg
将二者打包成boot.img格式的数据。
然后将这个打包好的数据,写入的usb中,指定要刷的目标为boot.img
。
所以,这个命令可以有两种用法:
1. fastboot boot boot.img2. fastboot boot kernel ramdisk //会先打包成boot.img
另外,可以看到,在使用mkbootimg
时,用到了很多参数,很多参数是可以通过特定选项指定的,比如kernel_offset
用-k
:
case 'k': kernel_offset = strtoul(optarg, 0, 16);
情景五
类似的flash:raw
:
else if(!strcmp(*argv, "flash:raw")) { char *pname = argv[1]; char *kname = argv[2]; char *rname = 0; require(3); if(argc > 3) { rname = argv[3]; skip(4); } else { skip(3); } data = load_bootable_image(kname, rname, &sz, cmdline); if (data == 0) die("cannot load bootable image"); fb_queue_flash(pname, data, sz); }
和fastboot boot
类似,先将kernel和ramdisk打包,在刷入,这里的不同是,你需要指定pname,即boot
,使用方式如下:
fastboot flash:raw boot kernel ramdisk
总结
至此,重要的几个命令分析清楚了,即通过fastboot协议,将数据写入到usb中。感觉完整的整个过程,需要再分析一下设备上的usb驱动接收到数据后的处理过程。但暂时没有相关的代码可以分析,先到此为止。
更多相关文章
- android绕过设备锁(device lock)
- 获取android手机移动设备号
- android 获取设备真实ip地址
- Android录音时,根据PCM数据获取音量值(单位分贝)
- Android 之往通讯录中添加数据和查询数据