适用范围: 本文主要针对Android Q的code

1. 文件位置

1.1 源文件

  1. Android原生:

    1
    system/bt/hci/src/btsnoop_net.cc
  2. qcm使用位置:

    1
    vendor/qcom/opensource/commonsys/system/bt/hci/src/btsnoop_net.cc

1.2 官方使用文档

1
system/bt/doc/btsnoop_net.md

1.3 编译情况

  1. 源文件build出的static library为libbt-hci(system/bt/hci/Android.bp)

    1
    2
    cc_library_static {
    name: "libbt-hci",
  2. 该static library会build进libbluetooth.so(system/bt/main/Android.bp)

    1
    2
    cc_library_shared {
    name: "libbluetooth",
  3. 另外,也会被libbt-stackstatic library(system/bt/stack/Android.bp)所用。

    1
    2
    cc_library_static {
    name: "libbt-stack",

2. 主要功能

btsoop_net通过本地TCP套接字公开蓝牙snoop日志,该套接字配合使用hcidump可实时调试HCI数据。
使用的端口号如下(system/bt/doc/network_ports.md):

1
2
TCP 8872 (hci/src/btsnoop_net.cc) - read live btsnoop logs
TCP 8873 (hci/src/hci_inject.cc) - inject HCI packets into HCI stream

备注:关于hci_inject.cc的分析(待续。。。。。。。)

3. 使用方法

  1. 官方使用方法如下,详细可参见btsnoop_net.md

“bt_stack.conf”中设置“btsnooplogoutput=true”启用。
重新启动stack,stack将侦听端口8872上的传入TCP连接。
在Linux主机上对hcidump使用此功能,运行:

1
2
$ adb forward tcp:8872 tcp:8872
$ nc localhost 8872 | hcidump -r /dev/stdin

  1. 本地测试校正后使用方法

经核实,源文件中并没有hcidump
。。。。。。。

4. 对外提供接口与调用情况

4.1 code中启用功能

需置BT_NET_DEBUGTRUE,否则进入接口函数之后直接返回。

4.2 接口函数

1
2
3
void btsnoop_net_open()
void btsnoop_net_write(const void* data, size_t length)
void btsnoop_net_close()

4.2.1 btsnoop_net_open

创建thread执行listen_fn_函数,主要功能由listen_fn_实现(listen_fn_的主要功能参见5)。

1
2
listen_thread_valid_ =
(pthread_create(&listen_thread_, NULL, listen_fn_, NULL) == 0);

4.2.2 btsnoop_net_write

如果存在client socket,就向其写入数据包

1
send(client_socket_, data, length, 0)

4.2.3 btsnoop_net_close

停止thread,关socket

4.3 调用情况

这些接口函数仅会在btsnoop.cc中被调用,调用的函数的对应情况如下

接口函数 调用函数
btsnoop_net_open start_up
btsnoop_net_write btsnoop_write_packet
btsnoop_net_close shut_down

4.4 log

logcat log相关过滤关键字"bt_snoop_net"。此file仅在出错时才会有log。

5. 起主要作用的listen\_fn\_

listen_fn_开启端口号为8872的localhost socket(127.0.0.7:8872),作为server端侦听client端的请求。

5.1 创建socketlisten_socket_

1
listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

5.2 创建local socket的地址结构

创建127.0.0.1:8872sockaddr_in

1
2
3
4
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(LOCALHOST_); //LOCALHOST_ = 0x7F000001, 127.0.0.1
addr.sin_port = htons(LISTEN_PORT_); //LISTEN_PORT_ = 8872

5.3 关联listen_socket_sockaddr_in地址结构

1
bind(listen_socket_, (struct sockaddr*)&addr, sizeof(addr)

5.4 监听listen_socket_

1
listen(listen_socket_, 10)

5.5 受理连接请求

1
client_socket = accept(listen_socket_, NULL, NULL)

5.6 写入snoop log的文件头

1
send(client_socket_, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16, 0)

6. qcm相对原生code添加的功能

主要差异体现在listen\_fn\_函数中

6.1 添加本地管道以终止thread

  1. 新建管道

    1
    2
    3
    4
    5
    6
    int self_pipe_fds[2];

    pipe2(self_pipe_fds, O_NONBLOCK);

    notification_listen_fd = self_pipe_fds[0];
    notification_write_fd = self_pipe_fds[1];
  2. 终止thread

    用以终止跑listen\_fn\_函数的listen_thread_

    • btsnoop_net_close中调用notify_listen_thread()

    • notify_listen_thread()notification_write_fdpipe

      1
      write(notification_write_fd, &buffer, 1)
    • listen\_fn\_中侦听notification_listen_fd的变化即返回

      1
      2
      3
      4
      5
      6
      select(fd_max + 1, &sock_fds, NULL, NULL, NULL);
      ...
      if((notification_listen_fd != -1) && FD_ISSET(notification_listen_fd, &sock_fds)) {
      LOG_WARN(LOG_TAG, "%s exting from listen_fn_ thread ", __func__);
      return NULL;
      }

6.2 创建并使用高通的socket

1
2
if (is_vndbtsnoop_enabled) {
listen_socket_local_ = local_snoop_socket_create();

创建、绑定以及监听socket的工作主要由local_snoop_socket_create函数实现,该函数定义于vendor\qcom\opensource\commonsys\bluetooth_ext\system_bt_ext\osi\src\vnd_log.cc

6.2.1 创建socket

1
int listen_socket_local = socket(AF_LOCAL, SOCK_STREAM, 0);

6.2.2 关联socket

1
2
3
//LOCAL_SOCKET_NAME == "bthcitraffic"
socket_local_server_bind(listen_socket_local, LOCAL_SOCKET_NAME,
ANDROID_SOCKET_NAMESPACE_ABSTRACT);

使用/tmp/bthcitrafficANDROID_SOCKET_NAMESPACE_ABSTRACT == /tmp/)绑定socket。

6.2.3 监听socket

1
listen(listen_socket_local, 1)

6.2.4 受理client连接请求

回到btsnoop_net.cc文件中(listen\_fn\_函数),如果listen_socket_local有变化,则受理client连接请求

1
accept(listen_socket_local_, (struct sockaddr *)&cliaddr, (socklen_t *)&length);

6.2.5 写入snoop log的文件头

1
write(client_socket, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16)

6.2.6 向btsnoop更新记录snoop log的socket文件描述符

调用update_snoop_fd(client_socket);将client socket保存至logfile_fd,替换录入log的文件描述符,并置sock_snoop_activetrue。之后蓝牙数据包即会写入此client socket,不再写入原来设定的snoop log文件。

sock_snoop_activetrue时,执行btsnoop.cc中执行open_next_snoop_file会直接返回。执行btsnoop_write_packet不会因packet个数限制而打开下一个文件。

6.3 使用fd_set管理多个文件描述符

6.3.1 将文件描述符加入集合

  1. 管道
1
FD_SET(notification_listen_fd, &save_sock_fds);
  1. local socket listen_socket_
1
FD_SET(listen_socket_, &save_sock_fds);
  1. 高通的socket
1
FD_SET(listen_socket_local_, &save_sock_fds);

6.3.2 侦听集合内文件描述符的变化并处理

1
2
3
4
5
6
7
8
9
10
11
for (;;) {

select(fd_max + 1, &sock_fds, NULL, NULL, NULL);
if ((listen_socket_local_ != -1) && FD_ISSET(listen_socket_local_, &sock_fds)) {
...
}else if((listen_socket_ != -1) && FD_ISSET(listen_socket_, &sock_fds)) {
...
} else if((notification_listen_fd != -1) && FD_ISSET(notification_listen_fd, &sock_fds)) {
...
}
}

7. qcm对listen_socket_local_socket的使用

vendor\qcom\opensource\commonsys\bluetooth\bt_logger\src\btsnoop_dump.csnoop_connect_to_source函数,该函数创建并连接server,并将该socket返回

7.1 创建socket

1
btsnoop_socket = socket(AF_LOCAL, SOCK_STREAM, 0);

7.2 设置socket地址结构

1
2
3
4
5
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sun_family = AF_LOCAL;
strlcpy(&serv_addr.sun_path[1], LOCAL_SOCKET_NAME, strlen(LOCAL_SOCKET_NAME) + 1);
addr_len = strlen(LOCAL_SOCKET_NAME) + 1;
addr_len += sizeof(serv_addr.sun_family);

LOCAL_SOCKET_NAME == "bthcitraffic"

7.3 关联socket和地址

1
connect(btsnoop_socket, (struct sockaddr *)&serv_addr, addr_len);

7.4 通过socket进行读写

void *snoop_dump_thread()函数中,先调用snoop_connect_to_source()创建并连接socket的server端再通过该socket进行读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
sk = snoop_connect_to_source();

//读取snoop log的文件头
bytes_recv = read_block (sk, &read_buf[0], 16);

//读取数据包
if (sk != -1)
{
do
{
ret = snoop_process(sk);
} while(ret != -1);
}