Android O 录制蓝牙HCI日志

    在Android Oreo之前,要想在一个release版本的系统中录制蓝牙HCI log,可以通过打开“开发者选项->开启蓝牙HCI信息收集日志”选项。默认蓝牙的HCI log生成文件名为“bsnoop_hci.log”,位于“sdcard”目录。但是从Android Oreo开始,google做了一些调整,原有的路子已经走不通了。如何解决呢?答案是,自己实现一个录制蓝牙HCI log的应用,从127.0.0.1:8872那里接受数据,保存到sdcard。


开发者选项的实现

    从UI上看,“开启蓝牙HCI信息收集日志”的开发者选项,是一个SwitchPreference。对于Oreo,具体的代码位于:

    packages/apps/Settings/src/com/android/settings/development/DevelopmentSettings.java

    DevelopmentSettings本身实现了很多interface,包括OnPreferenceChangeListener,因此HCI日志的开关也是在这里处理。

/*
 * Displays preferences for application developers.
 */
public class DevelopmentSettings extends RestrictedSettingsFragment
        implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
        OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable {
    private static final String TAG = "DevelopmentSettings";

具体到“开启蓝牙HCI信息收集日志”选项,它的实现是设置一个property:

private void writeBtHciSnoopLogOptions() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        SystemProperties.set(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY,
                Boolean.toString(mBtHciSnoopLog.isChecked()));
    }

仔细看上面的代码,adapter这个变量似乎是多余的。关于这一点,对比Android N的代码就可以看出来:

private void writeBtHciSnoopLogOptions() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        adapter.configHciSnoopLog(mBtHciSnoopLog.isChecked());
        Settings.Secure.putInt(getActivity().getContentResolver(),
                Settings.Secure.BLUETOOTH_HCI_LOG,
                mBtHciSnoopLog.isChecked() ? 1 : 0);
    }


蓝牙协议栈相关设置

    跟着代码一步步往下追就会发现,Nougat在打开“开启蓝牙HCI信息收集日志”选线之后,代码调用经过jni再到native,native部分就会立即开始记录蓝牙HCI日志。在Nougat以及更早的版本中(除使用bluez作为BT stack的版本),蓝牙HCI日志默认位于sdcard目录中(保存在系统的system/etc/bluetooth/bt_stack.conf文件中),因此只要能通过adb进入系统,所有人都可以获取。

到了Oreo,打开蓝牙HCI日志的方式变成了property。首先这是一个系统property,普通用户无法修改。其次,HCI日志的保存路径也变成了一个系统property,它的默认值保存在如下路径的文件中:

    system/bt/hci/src/btsnoop.cc

相关定义截取如下:

#define BTSNOOP_ENABLE_PROPERTY "persist.bluetooth.btsnoopenable"
#define BTSNOOP_PATH_PROPERTY "persist.bluetooth.btsnooppath"
#define DEFAULT_BTSNOOP_PATH "/data/misc/bluetooth/logs/btsnoop_hci.log"
#define BTSNOOP_MAX_PACKETS_PROPERTY "persist.bluetooth.btsnoopsize"

看到/data/这个路径,就应该知道老路子是走不通了。


Oreo抓取蓝牙HCI日志的解决方法

    老路子走不通,google还提供了另外一种方案——socket方式接收蓝牙HCI日志。Server部分的代码如下:

static void* listen_fn_(UNUSED_ATTR void* context) {
  int enable = 1;
  prctl(PR_SET_NAME, (unsigned long)LISTEN_THREAD_NAME_, 0, 0, 0);
  listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  //......
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(LOCALHOST_);
  addr.sin_port = htons(LISTEN_PORT_);
  if (bind(listen_socket_, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    LOG_ERROR(LOG_TAG, "%s unable to bind listen socket: %s", __func__,
              strerror(errno));
    goto cleanup;
  }
  if (listen(listen_socket_, 10) == -1) {
    LOG_ERROR(LOG_TAG, "%s unable to listen: %s", __func__, strerror(errno));
    goto cleanup;
  }
  for (;;) {
    int client_socket;
    OSI_NO_INTR(client_socket = accept(listen_socket_, NULL, NULL));
    if (client_socket == -1) {
      if (errno == EINVAL || errno == EBADF) {
        break;
      }
      LOG_WARN(LOG_TAG, "%s error accepting socket: %s", __func__,
               strerror(errno));
      continue;
    }
    /* When a new client connects, we have to send the btsnoop file header. This
     * allows a decoder to treat the session as a new, valid btsnoop file. */
    std::lock_guard lock(client_socket_mutex_);
    safe_close_(&client_socket_);
    client_socket_ = client_socket;
    OSI_NO_INTR(send(client_socket_, "btsnoop\0\0\0\0\1\0\0\x3\xea", 16, 0));
  }
cleanup:
  safe_close_(&listen_socket_);
  return NULL;
}

    Server端的实现很简单,它在127.0.0.1:8872上监听前来连接的client。首次连接上,server会将16字节的header发送给client,这是HCI日志格式的一部分。后续一旦有数据在HCI层流动,server会将数据copy一份送给client。


More

    通过socket获取蓝牙HCI日志的功能,Android从5.0就开始支持了。只是老路子还能用,谁还费神去想别的方法呢。

    Socket方式确认是可以走通了,可是还有一个疑问。Android的sepolicy越来越严格,难道google没有特意去限制连接的client么?

你可能感兴趣的:(bluedroid)