android adb 流程原理代碼分析(一)

    由於要用到adb的知識,但是對adb啥也不瞭解,看了下android的代碼,adb的源碼在system/core/adb下面,然後網上搜下了資料,發現很多大神的源碼分析,瞬間信心爆棚,把大神寫的博客都瀏覽了一遍,然後手動運行了下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等等。

    到此,我們對adb的理解就是,一種socket通信,每次發送apacket格式的數據包大笑大笑大笑,好吧,我們在這樣的認知下繼續學習adb。


    (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文件就傳輸了。流程理解了,我們再來看代碼,現在結果你知道了,流程你也懂了,再來看源碼,是不是容易理解了呢。


    (5)同樣,我們看代碼也是逆向的看,這樣利於我們理解,不會被源碼看到暈乎乎,上面流程懂了,知道了每次是以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的數據。

    上面不是有adb push命令嗎,我們根據這個流程,看看handle_packet是否是跟我們預期的響應流程一樣。

    (5.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的狀態大笑大笑

        (5.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,創建事件監聽線程,註冊回調響應函數,亂七八糟的偷笑......

      6. 然後就是一系列的代碼流程了,頭暈了,我們下節再來詳細理一理這個,爲啥要按照你說的這個流程走,爲啥你說走到這裏,調用這個函數,憑啥相信你,下節見......



發佈了74 篇原創文章 · 獲贊 49 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章