Android ADB 原始碼分析(三)
前言
之前分析的兩篇文章
寫完之後,都沒有寫到相關的實現程式碼,這篇文章寫下ADB的通訊流程的一些細節
看這篇文章之前,請先閱讀
Linux的SOCKET程式設計詳解 - 江召偉 - 部落格園
對socket通訊有簡單的瞭解
1、ADB基本通訊
理解:
(1)adb的本質,就是socket的通訊,通過secket傳送資料及檔案
(2)adb傳送是以每個固定格式的包傳送的資料,包的格式如下:
#define A_SYNC 0x434e5953 #define A_CNXN 0x4e584e43 #define A_OPEN 0x4e45504f #define A_OKAY 0x59414b4f #define A_CLSE 0x45534c43 #define A_WRTE 0x45545257 #define A_AUTH 0x48545541 struct amessage { unsigned command; /* command identifier constant */ unsigned arg0; /* first argument */ unsigned arg1; /* second argument */ unsigned data_length; /* length of payload (0 is allowed) */ unsigned data_check; /* checksum of data payload */ unsigned magic; /* command ^ 0xffffffff */ }; struct apacket { apacket *next; unsigned len; unsigned char *ptr; amessage msg; unsigned char data[MAX_PAYLOAD]; };
傳送的包格式為apacket格式,其中msg為訊息部分,data為資料部分。msg的訊息型別有很多種,包括A_SYNC, A_CNXN, A_OPEN, A_OKAY等等。
(3)adb給我們預留了除錯的資訊,我們只需要在adb.h中定義指定的巨集,即可看到每次資料的傳輸過程:
#define DEBUG_PACKETS 1
開啟除錯資訊後,我們可以看到傳輸過程中的細節,在串列埠列印裡面。
(4)我們使用adb push命令,來跟蹤分析下這個apacket資料是怎樣傳輸的:
我們以adb push profile /命令為例,在串列埠我們可以看見如下詳細的傳輸資訊:
status command arg0 arg1 len data
recv: OPEN 00141028 00000000 0006 "sync:."
send: OKAY 0000003e 00141028 0000 ""
recv: WRTE 00141028 0000003e 0009 "STAT..../"
send: OKAY 0000003e 00141028 0000 ""
send: WRTE 0000003e 00141028 0010 "STAT.A......[oHZ"
recv: OKAY 00141028 0000003e 0000 ""
recv: WRTE 00141028 0000003e 0027 "SEND..../profile,33206DATA....2D
send: OKAY 0000003e 00141028 0000 ""
send: WRTE 0000003e 00141028 0008 "OKAY...."
recv: OKAY 00141028 0000003e 0000 ""
recv: WRTE 00141028 0000003e 0008 "QUIT...."
send: OKAY 0000003e 00141028 0000 ""
send: CLSE 00000000 00141028 0000 ""
recv: CLSE 00141028 0000003e 0000 ""
以上recv表示接收的資料包,send表示回傳的資料包。後面五個分別為資料包的資料欄位值(command arg0 arg1 len data),這樣資料我們還是不夠直觀,我們翻譯成更加直接的資料輔以文字解釋
這樣是不是容易理解多了呢,經過這樣的資料傳送,我們就通過adb push命令把本地的profile檔案推送到遠端裝置的根目錄了。哇..... 原來這麼簡單,一個profile檔案就傳輸了。流程理解了,我們再來看程式碼,現在結果你知道了,流程你也懂了,再來看原始碼,是不是容易理解了呢。
2、程式碼分析
我們看程式碼也是逆向的看,這樣利於我們理解,不會被原始碼看到暈乎乎,上面流程懂了,知道了每次是以apacket的格式傳送的,我們先來研究這個apacket的接收與傳送函式。
接收函式handle_packet
void handle_packet(apacket *p, atransport *t)
{
asocket *s;
D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0],
((char*) (&(p->msg.command)))[1],
((char*) (&(p->msg.command)))[2],
((char*) (&(p->msg.command)))[3]);
print_packet("recv", p);
switch(p->msg.command){
case A_SYNC:
if(p->msg.arg0){
send_packet(p, t);
if(HOST) send_connect(t);
} else {
t->connection_state = CS_OFFLINE;
handle_offline(t);
send_packet(p, t);
}
return;
case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
/* XXX verify version, etc */
if(t->connection_state != CS_OFFLINE) {
t->connection_state = CS_OFFLINE;
handle_offline(t);
}
parse_banner((char*) p->data, t);
if (HOST || !auth_enabled) {
handle_online(t);
if(!HOST) send_connect(t);
} else {
send_auth_request(t);
}
break;
case A_AUTH:
if (p->msg.arg0 == ADB_AUTH_TOKEN) {
t->key = adb_auth_nextkey(t->key);
if (t->key) {
send_auth_response(p->data, p->msg.data_length, t);
} else {
/* No more private keys to try, send the public key */
send_auth_publickey(t);
}
} else if (p->msg.arg0 == ADB_AUTH_SIGNATURE) {
if (adb_auth_verify(t->token, p->data, p->msg.data_length)) {
adb_auth_verified(t);
t->failed_auth_attempts = 0;
} else {
if (t->failed_auth_attempts++ > 10)
adb_sleep_ms(1000);
send_auth_request(t);
}
} else if (p->msg.arg0 == ADB_AUTH_RSAPUBLICKEY) {
adb_auth_confirm_key(p->data, p->msg.data_length, t);
}
break;
case A_OPEN: /* OPEN(local-id, 0, "destination") */
if (t->online) {
char *name = (char*) p->data;
name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
s = create_local_service_socket(name);
if(s == 0) {
send_close(0, p->msg.arg0, t);
} else {
s->peer = create_remote_socket(p->msg.arg0, t);
s->peer->peer = s;
send_ready(s->id, s->peer->id, t);
s->ready(s);
}
}
break;
case A_OKAY: /* READY(local-id, remote-id, "") */
if (t->online) {
if((s = find_local_socket(p->msg.arg1))) {
if(s->peer == 0) {
s->peer = create_remote_socket(p->msg.arg0, t);
s->peer->peer = s;
}
s->ready(s);
}
}
break;
case A_CLSE: /* CLOSE(local-id, remote-id, "") */
if (t->online) {
if((s = find_local_socket(p->msg.arg1))) {
s->close(s);
}
}
break;
case A_WRTE:
if (t->online) {
if((s = find_local_socket(p->msg.arg1))) {
unsigned rid = p->msg.arg0;
p->len = p->msg.data_length;
if(s->enqueue(s, p) == 0) {
D("Enqueue the socket\n");
send_ready(s->id, rid, t);
}
return;
}
}
break;
default:
printf("handle_packet: what is %08x?!\n", p->msg.command);
}
put_apacket(p);
}
哇,這個函式好像不復雜
一個函式,然後解析apacket *p資料,根據msg.command的命令值, 然後對應不同的case,有著不同的響應。事實上也就是這樣,這個函式主要就是根據不同的訊息型別,來處理這個apacket的資料。
下面解析下上面push命令的過程
1、OPEN響應
recv: OPEN 00141028 00000000 0006 "sync:."
send: OKAY 0000003e 00141028 0000 ""
接收到了OPEN的訊息,然後附帶了一個sync的資料,我們看看是如何響應的。
case A_OPEN: /* OPEN(local-id, 0, "destination") */
if (t->online) {
char *name = (char*) p->data;
name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0;
s = create_local_service_socket(name);
if(s == 0) {
send_close(0, p->msg.arg0, t);
} else {
s->peer = create_remote_socket(p->msg.arg0, t);
s->peer->peer = s;
send_ready(s->id, s->peer->id, t);
s->ready(s);
}
}
break;
呼叫create_local_service_socket(“sync”);
fd = service_to_fd(name);
//建立本地socket,併為這個socket建立資料處理執行緒file_sync_service
ret = create_service_thread(file_sync_service, NULL);
//把這個本地socket關聯到結構asocket *s
s = create_local_socket(fd);
呼叫create_remote_socket(p->msg.arg0, t); //把遠端的socket也與這個結構體asocket 關聯。
如上兩個函式呼叫,主要是初始化本地的socket對,本地socket用來跟後臺服務執行緒之間的通訊,以及跟對應命令的後臺服務執行緒通訊。初始化adb通訊的環境。其中asocket *s為本地socket與遠端socket的一個關聯結構體,其中s儲存的是本地socket的資訊,s->peer儲存的是遠端socket相關的資訊。
send_ready(s->id, s->peer->id, t); 然後傳送OKAY給PC端。
static void send_ready(unsigned local, unsigned remote, atransport *t)
{
D("Calling send_ready \n");
apacket *p = get_apacket();
p->msg.command = A_OKAY;
p->msg.arg0 = local;
p->msg.arg1 = remote;
send_packet(p, t);
}
這個與我們看到的流程相符合。接收到OPEN的訊息,初始化一些狀態,然後返回一個OKAY的狀
2、WRITE響應
recv: WRTE 00141028 0000003e 0009 "STAT..../"
send: OKAY 0000003e 00141028 0000 ""
send: WRTE 0000003e 00141028 0010 "STAT.A......[oHZ"
接收到了WRITE的訊息,順帶了一個查詢STAT的資料,我們看看是如何響應的:
case A_WRTE:
if (t->online) {
if((s = find_local_socket(p->msg.arg1))) {
unsigned rid = p->msg.arg0;
p->len = p->msg.data_length;
if(s->enqueue(s, p) == 0) {
D("Enqueue the socket\n");
send_ready(s->id, rid, t);
}
return;
}
}
break;
先通過引數p->msg.arg1找到我們在OPEN的時候建立的結構體資訊asocket *s, 然後處理本地socket佇列中的資料(s為本地,s->peer為遠端)
s->enqueue(s, p)即為之前 關聯的函式local_socket_enqueue,其在create_local_socket(fd); 的時候設定。
static int local_socket_enqueue(asocket *s, apacket *p)
{
D("LS(%d): enqueue %d\n", s->id, p->len);
p->ptr = p->data;
/* if there is already data queue'd, we will receive
** events when it's time to write. just add this to
** the tail
*/
if(s->pkt_first) {
goto enqueue;
}
/* write as much as we can, until we
** would block or there is an error/eof
*/
while(p->len > 0) {
int r = adb_write(s->fd, p->ptr, p->len);
if(r > 0) {
p->len -= r;
p->ptr += r;
continue;
}
if((r == 0) || (errno != EAGAIN)) {
D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) );
s->close(s);
return 1; /* not ready (error) */
} else {
break;
}
}
if(p->len == 0) {
put_apacket(p);
return 0; /* ready for more data */
}
enqueue:
p->next = 0;
if(s->pkt_first) {
s->pkt_last->next = p;
} else {
s->pkt_first = p;
}
s->pkt_last = p;
/* make sure we are notified when we can drain the queue */
fdevent_add(&s->fde, FDE_WRITE);
return 1; /* not ready (backlog) */
}
我們通過adb_write(s->fd, p->ptr, p->len)把要處理的資料,寫入到本地socket對應的fd中,等待處理。
然後呼叫send_ready(s->id, rid, t);返回一個OKAY的狀態
我們把待處理的資料adb_write之後,又是在哪裡處理的呢,我們之前在建立本地socket的時候,就建立了一個執行緒,對應的處理socket資料的函式file_sync_service。
我們來看看file_sync_service函式是如何處理的
void file_sync_service(int fd, void *cookie)
{
syncmsg msg;
char name[1025];
unsigned namelen;
char *buffer = malloc(SYNC_DATA_MAX);
if(buffer == 0) goto fail;
for(;;) {
D("sync: waiting for command\n");
if(readx(fd, &msg.req, sizeof(msg.req))) {
fail_message(fd, "command read failure");
break;
}
namelen = ltohl(msg.req.namelen);
if(namelen > 1024) {
fail_message(fd, "invalid namelen");
break;
}
if(readx(fd, name, namelen)) {
fail_message(fd, "filename read failure");
break;
}
name[namelen] = 0;
msg.req.namelen = 0;
D("sync: '%s' '%s'\n", (char*) &msg.req, name);
switch(msg.req.id) {
case ID_STAT:
if(do_stat(fd, name)) goto fail;
break;
case ID_LIST:
if(do_list(fd, name)) goto fail;
break;
case ID_SEND:
if(do_send(fd, name, buffer)) goto fail;
break;
case ID_RECV:
if(do_recv(fd, name, buffer)) goto fail;
break;
case ID_QUIT:
goto fail;
default:
fail_message(fd, "unknown command");
goto fail;
}
}
fail:
if(buffer != 0) free(buffer);
D("sync: done\n");
adb_close(fd);
}
原來在這裡處理的資料,終於找到你, 我們收到的訊息是檢視路徑是否存在,這裡對應的就是ID_STAT,還有其他的訊息處理,比如ID_SEND,ID_RECV,ID_QUIT,望文生義,我們就不具體解釋了。我們還是看看ID_STAT對應的處理吧do_stat(fd, name)。
static int do_stat(int s, const char *path)
{
syncmsg msg;
struct stat st;
msg.stat.id = ID_STAT;
if(lstat(path, &st)) {
msg.stat.mode = 0;
msg.stat.size = 0;
msg.stat.time = 0;
} else {
msg.stat.mode = htoll(st.st_mode);
msg.stat.size = htoll(st.st_size);
msg.stat.time = htoll(st.st_mtime);
}
return writex(s, &msg.stat, sizeof(msg.stat));
}
這裡就是判斷路徑是否存在的邏輯了,這個就是我們想要的,我們把判斷的結果儲存在msg.stat, 然後把對應的結果寫回去writex。
我們把檢測的狀態writex之後,但是這個資料還沒有傳送回PC端啊,是在哪裡傳送回去的呢,我們繼續跟蹤
我們在create_local_socket建立本地socket的時候,順便還註冊了一個回撥函式local_socket_event_func
static void local_socket_event_func(int fd, unsigned ev, void *_s)
{
asocket *s = _s;
D("LS(%d): event_func(fd=%d(==%d), ev=%04x)\n", s->id, s->fd, fd, ev);
/* put the FDE_WRITE processing before the FDE_READ
** in order to simplify the code.
*/
if(ev & FDE_WRITE){
apacket *p;
while((p = s->pkt_first) != 0) {
while(p->len > 0) {
int r = adb_write(fd, p->ptr, p->len);
if(r > 0) {
p->ptr += r;
p->len -= r;
continue;
}
if(r < 0) {
/* returning here is ok because FDE_READ will
** be processed in the next iteration loop
*/
if(errno == EAGAIN) return;
if(errno == EINTR) continue;
}
D(" closing after write because r=%d and errno is %d\n", r, errno);
s->close(s);
return;
}
if(p->len == 0) {
s->pkt_first = p->next;
if(s->pkt_first == 0) s->pkt_last = 0;
put_apacket(p);
}
}
/* if we sent the last packet of a closing socket,
** we can now destroy it.
*/
if (s->closing) {
D(" closing because 'closing' is set after write\n");
s->close(s);
return;
}
/* no more packets queued, so we can ignore
** writable events again and tell our peer
** to resume writing
*/
fdevent_del(&s->fde, FDE_WRITE);
s->peer->ready(s->peer);
}
if(ev & FDE_READ){
apacket *p = get_apacket();
unsigned char *x = p->data;
size_t avail = MAX_PAYLOAD;
int r;
int is_eof = 0;
while(avail > 0) {
r = adb_read(fd, x, avail);
D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail);
if(r > 0) {
avail -= r;
x += r;
continue;
}
if(r < 0) {
if(errno == EAGAIN) break;
if(errno == EINTR) continue;
}
/* r = 0 or unhandled error */
is_eof = 1;
break;
}
D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n",
s->id, s->fd, r, is_eof, s->fde.force_eof);
if((avail == MAX_PAYLOAD) || (s->peer == 0)) {
put_apacket(p);
} else {
p->len = MAX_PAYLOAD - avail;
r = s->peer->enqueue(s->peer, p);
D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd, r);
if(r < 0) {
/* error return means they closed us as a side-effect
** and we must return immediately.
**
** note that if we still have buffered packets, the
** socket will be placed on the closing socket list.
** this handler function will be called again
** to process FDE_WRITE events.
*/
return;
}
if(r > 0) {
/* if the remote cannot accept further events,
** we disable notification of READs. They'll
** be enabled again when we get a call to ready()
*/
fdevent_del(&s->fde, FDE_READ);
}
}
/* Don't allow a forced eof if data is still there */
if((s->fde.force_eof && !r) || is_eof) {
D(" closing because is_eof=%d r=%d s->fde.force_eof=%d\n", is_eof, r, s->fde.force_eof);
s->close(s);
}
}
if(ev & FDE_ERROR){
/* this should be caught be the next read or write
** catching it here means we may skip the last few
** bytes of readable data.
*/
// s->close(s);
D("LS(%d): FDE_ERROR (fd=%d)\n", s->id, s->fd);
return;
}
}
我們看後面if(ev & FDE_READ)部分:
adb_read(fd, x, avail);把資料讀出來,然後呼叫r = s->peer->enqueue(s->peer, p);,即把資料傳送給遠端socket的佇列處理。(s->speer即遠端端,之前已經說明)
s->peer->enqueue函式即remote_socket_enqueue
static int remote_socket_enqueue(asocket *s, apacket *p)
{
D("entered remote_socket_enqueue RS(%d) WRITE fd=%d peer.fd=%d\n",
s->id, s->fd, s->peer->fd);
p->msg.command = A_WRTE;
p->msg.arg0 = s->peer->id;
p->msg.arg1 = s->id;
p->msg.data_length = p->len;
send_packet(p, s->transport);
return 1;
}
這樣我們就把STAT的結果,通過WRITE返回給了PC端
這個與我們看到的流程也是相符的,接收到WRITE(STAT)的訊息,先返回一個OKAY的狀態,在返回WRITE(STAT)的結果。
我們可以觀察之前的資料接收及傳送流程,可以發現每次一個WRITE訊息,後面都是返回一個OKAY WRITE訊息。
貼了這麼多的程式碼,是不是有點暈了,再貼就真的看不下去了,我們下面重新來理一理思路。
1. adb其實就是個socket通訊,資料發過來發過去。
2. adb每次都是傳送的一個數據包,資料結構是struct apacket,其中包含msg訊息部分,及data資料部分。
3. 從PC跟device通訊的過程,有一條協議流程,通過不斷的資料交互發送,實現資料檔案傳遞。
4. 我們可以定義 #define DEBUG_PACKETS 1 這樣可以看到socket通訊的資料傳送過程。
5. socket資料建立傳輸過程,會建立socket,建立事件監聽執行緒,註冊回撥響應函式,亂七八糟的....
如果覺得不錯,請關注公眾號【嵌入式Linux】,謝謝