网站建设研究背景,wordpress百度移动落地页,wordpress 怎么添加即时联系窗口,对于网站运营应该如何做前言
之前分析的两篇文章
Android Adb 源码分析(一)
嵌入式Linux#xff1a;Android root破解原理#xff08;二#xff09; 写完之后#xff0c;都没有写到相关的实现代码#xff0c;这篇文章写下ADB的通信流程的一些细节
看这篇文章之前#xff0c;请先阅读
Linux…前言
之前分析的两篇文章
Android Adb 源码分析(一)
嵌入式LinuxAndroid root破解原理二 写完之后都没有写到相关的实现代码这篇文章写下ADB的通信流程的一些细节
看这篇文章之前请先阅读
Linux的SOCKET编程详解 - 江召伟 - 博客园
对socket通信有简单的了解
1、ADB基本通信
理解
1adb的本质就是socket的通信通过secket传送数据及文件
2adb传送是以每个固定格式的包发送的数据包的格式如下
#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 0x48545541struct 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等等。
3adb给我们预留了调试的信息我们只需要在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 queued, we will receive** events when its 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_SENDID_RECVID_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, r0?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. Theyll** be enabled again when we get a call to ready()*/fdevent_del(s-fde, FDE_READ);}}/* Dont 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端
这个与我们看到的流程也是相符的接收到WRITESTAT的消息先返回一个OKAY的状态在返回WRITESTAT的结果。
我们可以观察之前的数据接收及发送流程可以发现每次一个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】,谢谢