ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小米手机蓝牙HCI log抓取与解析实战指南

小米手机蓝牙HCI log抓取与解析实战指南 搞蓝牙开发这几年我最大的体会就是出了问题如果只看应用层log很多时候你会发现像在捉迷藏——明明回调里报了失败但为什么失败、卡在哪一步上层完全看不到。这时候HCI log就是那个“黑匣子”把协议栈和蓝牙芯片之间每一句对话都记录下来。尤其在做小米手机适配的时候HCI log的抓取与解析几乎是必修课学会了它排查配对失败、回连不上、广播扫描不到这类问题效率能提升一大截。这篇文章不绕弯子直接讲小米手机上怎么抓HCI log、怎么用Wireshark或者SQLite把日志拆开看懂再把我踩过的一些坑和排查思路一并写出来。不管是刚接触蓝牙开发的应用层工程师还是已经在搞协议栈、固件的同学这套流程拿过去就能用5分钟就能跑通。1. 为什么蓝牙开发一定要学会抓HCI log1.1 HCI log到底记录了什么先把概念捋清楚。蓝牙协议栈分两大块Host和Controller。Host跑在应用处理器上负责协议栈上层的逻辑、GATT、L2CAP这些Controller就是蓝牙射频芯片那部分负责物理层、链路层这些底层活。中间隔着一道非常标准的接口叫HCIHost Controller Interface。我们用手机做蓝牙开发的时候上层App调用BluetoothAdapter、BluetoothGatt这些API指令会一层一层往下走最后通过HCI命令发给芯片芯片处理完再通过HCI事件把结果返回给协议栈。HCI log记录的就是Host和Controller之间这段原始交互。一句话说HCI log是蓝牙开发里最接近物理真相的一份数据。应用层的报错可以被捕获、被吞掉但HCI这一层每一帧命令和事件都在谁先发的、谁回的、回了什么错误码一目了然。我在实际工作中经常会遇到“App回调成功但实际没连上”的诡异现象这种问题如果不抓HCI log基本只能靠猜。1.2 什么场景该开HCI log附案例不是所有问题都需要HCI log但下面这几类场景我强烈建议你先别急着改代码先把HCI log抓了再说搜不到设备应用层startScan没回调或者是扫描结果时有时无。这可能是App代码的问题也可能是Controller根本没上报广播包。HCI log里能清楚看到有没有下发LE Set Scan Enable命令、有没有LE Advertising Report事件回来。回连失败上层显示发起连接但很快就报失败。HCI log里能看到Link Layer Connection Complete事件的状态码是超时、被拒绝还是资源不足错误码直接指向原因。配对绑定异常配对过程中SMP层交换密钥失败、PIN码弹窗不出现、绑定后掉绑定。HCI log能够还原整个配对流程走到哪一步挂掉。服务发现拿不到连上了但读不到Service这通常是ATT层的问题其实和底层的连接参数也有关系。吞吐量差、延迟高连接间隔、从机延迟到底协商成了多少HCI log里的Connection Update事件会给出准确数值。我还遇到过一个挺典型的例子有同学在开发蓝牙扫描功能时ListView一直不显示设备他以为是UI刷新或者列表适配器写错了折腾了好几天。后来抓了HCI log才发现扫描指令压根没有下到Controller问题出在上层调用扫描的时机和蓝牙开关状态上。这种问题如果只看业务代码很容易跑偏。2. 小米手机抓HCI log5分钟快速上手2.1 第一步打开开发者选项和蓝牙日志小米手机的系统版本比较多MIUI跟现在的澎湃HyperOS入口稍微有点区别但大方向一致。先去“设置 - 我的设备 - 全部参数与信息”里找“版本号”或者“OS版本号”连续点击7次左右系统会提示你进入了开发者模式。有的机型在开启过程中会要求输入锁屏密码或者登录小米账号这个按提示操作就行。然后回到“设置 - 更多设置 - 开发者选项”里往下找蓝牙相关的开关。小米给这个功能起过好几个名字有的版本叫“蓝牙HCI信息收集日志”有的版本叫“Bluetooth HCI snoop log”还有的翻译成“蓝牙HCI日志”。把它打开即可。这里有个小提醒不同系统版本对开发者选项里的这个开关做了不同程度的隐藏。有些版本需要先点开“日志输出级别”之类的选项才会显示完整列表找不到的话可以往上翻一翻或者把开发者选项整个列表截图下来挨个找不丢人。2.2 第二步正确触发并保存log开关打开之后不要急着去复现问题。很多第一次用的人都会犯一个错误打开开关后直接就做连接测试结果回头发现log文件根本不存在。原因很简单HCI日志的初始化是跟着蓝牙协议栈启动走的。为了确保log文件被正确创建这个时候应该先关闭蓝牙再重新打开。具体操作顺序我建议这样来打开开发者选项里的蓝牙HCI日志开关。到快捷开关面板里把蓝牙关掉等几秒钟。再打开蓝牙。这时候再回到你的App里开始复现问题抓取时间尽量控制在2分钟左右。抓完之后回到开发者选项把蓝牙HCI日志开关关掉再去导出文件。记得在操作结束后立刻关闭日志不要让log一直在后台写入否则文件滚动覆盖以后反而把关键过程冲掉了。2.3 第三步把log从手机里完整导出导出方式分两种。第一种最直接手机连上电脑后用adb把文件拉出来。小米的log文件路径跟原生Android不太一样不同版本的路径有区别我比较建议直接用find命令搜省得记错路径浪费时间。adb devices adb shell find /sdcard -name btsnoop_hci* 2/dev/null正常情况下你会在类似下面这个路径找到文件/sdcard/MIUI/debug_log/common/logs/bluetooth/btsnoop_hci.log找到以后直接pull出来adb pull /sdcard/MIUI/debug_log/common/logs/bluetooth/btsnoop_hci.log ./btsnoop_hci.log第二种方式适合文件路径奇葩或者ADB权限受阻的情况。小米的部分机型会把这个文件写到应用私有目录或者data分区普通adb权限读不到这时候可以去开发者选项里找到“日志抓取”或者“生成日志”相关入口单独生成一份蓝牙日志压缩包再通过文件管理器导出到电脑。这种方式生成的是一个zip压缩包里面包含了蓝牙相关的日志文件HCI log就在其中。3. 用Wireshark拆解HCI log的关键姿势3.1 文件打不开先区分btsnoop格式和SQLite格式很多同学第一次把btsnoop_hci.log拿到手直接拖进Wireshark结果发现要么打不开要么打开全是乱码第一反应就是抓包抓坏了。其实真不一定是坏文件而是文件格式的问题。Android原生的HCI日志有两种存储格式。老版本和默认情况下采用标准的btsnoop格式Wireshark可以直接打开。但某几个版本或者部分定制系统会把日志存成SQLite数据库格式文件名虽然还叫.log但实际是一个数据库文件。区分方法很简单在电脑上执行file btsnoop_hci.log如果显示是SQLite数据库那就先用系统自带的小工具转一下格式再拖进Wireshark。将手机连上电脑后执行adb shell btsnoop -r /sdcard/MIUI/debug_log/common/logs/bluetooth/btsnoop_hci.log -w /sdcard/MIUI/debug_log/common/logs/bluetooth/btsnoop_hci.snoop adb pull /sdcard/MIUI/debug_log/common/logs/bluetooth/btsnoop_hci.snoop ./btsnoop_hci.snoop转换完的.snoop文件就是标准btsnoop格式Wireshark双击就能打开。这个命令是Android系统自带的不用额外装工具我实测过小米手机上可以直接用。3.2 高频过滤器和对应场景Wireshark打开HCI log之后第一眼看到的内容会比较劝退全是十六进制数据包。别慌先把过滤条件用起来一切就清楚了。对于HCI层的数据包最常见的几个显示过滤器是hci_h4.cmd只看Host发给Controller的HCI命令。hci_h4.evt只看Controller返回给Host的事件。hci_h4.aco只看ACL数据也就是真正承载业务的数据包GATT读写也是走这里的。btl2cap只看L2CAP层适合查连接建立、信道分配的问题。btatt只看ATT层适合查Service发现、读写特征值。btsmp只看配对和加密相关的SMP协议流程。btsdp只看经典蓝牙的服务发现协议。比如你要查设备和手机为什么连不上先看hci_h4.evt里有没有 Connection Complete 事件再看错误码是多少。错误码0x00是成功0x3E是连接被拒绝且资源不足0x08是连接超时这些在Wireshark里点开事件包就能看到完整解释。如果查广播扫描问题推荐用这个组合过滤条件hci_h4.cmd bthci_cmd.opcode 0x200c这条命令对应的是LE Set Scan Enable。如果Host下发了这个命令说明App确实调用了扫描问题可能出在Controller侧如果压根没抓到这条命令那就要回到应用层看扫描有没有正确发起。3.3 经典问题定位流程示例我拿一个典型的BLE回连失败案例来演示。现象是App发起连接后过几秒回调显示失败。打开HCI log后先输入过滤条件hci_h4.evt把它收到的所有事件按时间顺序过一遍。正常情况下能看到LE Connection Complete事件status为0x00表示连接成功建立。如果status是非零值那问题基本定性在底层。接下来再根据错误码去排查是距离太远、设备进入了不可连接状态还是射频干扰。如果HCI层看起来一切正常连接也成功了但App上层依然报失败那就要看ATT层。输入过滤条件btatt看看有没有发起Service Discovery有没有Exchange MTU有没有正常的Read请求和响应。如果只有请求没有响应说明远端设备响应卡住了问题可能出在对端设备而非手机。这整个排查过程如果用纯代码debug方式可能要做大量日志埋点还未必能看到底层但有了HCI log两层协议栈的交互过程全部透明定位路径清晰很多。4. 不想装Wireshark直接用SQLite查HCI log4.1 表结构和基础查询示例如果btsnoop_hci.log是以SQLite格式保存的其实你不用转换也可以直接分析而且用命令行操作感觉反而更清爽。前提是你的电脑上装有sqlite3工具macOS和Linux基本自带Windows可以装一个很小。打开文件先看一下里面有什么sqlite3 btsnoop_hci.log .tables从Android源码的结构看这个数据库里一般会有一张以HCI报文为核心的表字段主要围绕时间戳、数据类型、报文内容来组织。不同机型或者不同Android版本的字段名会有些出入所以进入后先看一眼表结构更稳妥sqlite3 btsnoop_hci.log .schema找到表结构之后就可以用SQL直接查。比如想看看某个时间段内的HCI事件可以按时间戳排序sqlite3 btsnoop_hci.log SELECT timestamp, type, length(content) FROM hci ORDER BY timestamp LIMIT 50;因为HCI报文是以十六进制形式存起来的直接看可能不直观但通过熟悉SQL查询至少能快速定位到问题发生的时间点再回到Wireshark里去看那几秒的详细交互。4.2 用“命令多表关联”替代GUI的常用套路我在电脑没装Wireshark的紧急场合下会直接用SQLite配合Python脚本处理。思路很简单先从SQLite里把指定时间段的报文dump成文本文件再写个小脚本把十六进制内容组装成标准的pcap文件最后回到Wireshark里解析。这个流程的核心不是脚本本身而是你可以批量处理很多个log文件。比如测试人员一口气扔给你十几个连接失败的log用SQLite脚本批量提取每个文件里的Connection Complete事件把所有状态码聚合成一张表很快就能看出来是不是某个错误码在反复出现。这比一个一个手工打开Wireshark效率高太多了。import sqlite3 import sys conn sqlite3.connect(sys.argv[1]) cursor conn.cursor() cursor.execute(SELECT timestamp, type, content FROM hci ORDER BY timestamp LIMIT 100) for row in cursor.fetchall(): print(row[0], row[1], row[2][:80]) conn.close()这段代码只是个示例实际使用的时候重点是把type字段对应到HCI包类型然后对content做解析。等你处理过几个文件以后会发现SQLite方式特别适合做批量统计和初筛。5. 小米手机HCI log抓取常见问题与排查速查表5.1 文件找不到、内容为空的5个原因我遇到过很多次HCI log拉了文件结果发现是0字节或者文件压根不存在的状况。把常见的几个原因总结一下顺序基本也是按出现频率排的第一开关打开后没有重新开关蓝牙。这个最普遍协议栈没有重新初始化log文件根本没建起来。第二系统版本对日志做了延迟写入刚复现完立刻去拉文件内容还没落盘。第三种情况是路径找错了小米把文件写在 /sdcard/Android/data/ 相关目录或者应用私有目录普通find命令没搜到不代表文件不存在。第四种情况有点隐蔽HCI日志如果文件超过一定大小系统会自动滚动覆盖旧数据。如果你抓了很长时间才去关开关关键的复现过程可能已经被覆盖掉了。第五种情况比较无奈某些系统阉割版本把蓝牙调试日志功能裁剪掉了开关虽然有但不会真正写文件。如果以上原因都排除了建议换个更靠谱的入口去开发者选项里找“日志抓取”生成一次完整的压缩日志包再从中提取蓝牙日志。5.2 权限不足和文件损坏的处理权限不足的问题主要出在非开发版系统上。MIUI稳定版的adb权限有限如果文件路径在 /data/misc/bluetooth/ 下面直接adb pull会提示Permission denied。这时候不要硬刚几个思路供参考。第一个思路是先用find找到sdcard下可读的副本如果系统已经帮你copy了一份到公共目录直接拉副本。第二个思路是走“日志抓取”生成zip这是小米比较推荐的路径生成的压缩包通常在sdcard的MIUI/debug_log目录下手机自带的文件管理器就能看到再传到电脑解压。第三个思路是让设备连接电脑后在手机上打开USB文件传输模式MTP直接从文件夹界面手动拷贝。如果日志文件拉出来了但Wireshark打开报错先用file命令确认格式。如果是SQLite但表结构异常多半是抓取过程中系统断电或者空间不足导致写入中断这种log基本废了重新抓一次更快。5.3 其他高频“坑”与解决方案这里整理一份问题速查表都是实际开发中反复遇到的现象可能原因处理办法日志文件一直0字节蓝牙没有重新开关或该版本未真正启用日志关闭再开启蓝牙重新复现Wireshark打不开文件是SQLite格式而非btsnoop格式用btsnoop命令转换或直接用sqlite3打开只有HCI命令没有HCI事件抓取时间太短或Controller侧日志被裁剪拉长抓包时间使用日志抓取完整压缩包时间戳显示为UTC差8小时Wireshark默认显示UTC时间在Wireshark里调整时间显示为本地时间关键过程被覆盖抓取时间过长超过文件大小上限缩短到2分钟内复现完立刻关闭日志adb pull权限不足文件在应用私有目录或data分区使用日志抓取生成zip或换可读路径我额外说一个经验小米手机的蓝牙日志在部分机型上会同时生成多个文件比如btsnoop_hci.log和一串带数字的cap文件。真正关键的往往是那个btsnoop开头的其他文件有时候是空壳如果打开了一片空白先看看是不是选错文件了。6. 关于小米手机抓HCI log的一些个人心得6.1 不同版本系统路径差异大别死记硬背在小米手机上抓HCI log我最深刻的体会就是路径和界面位置一直在变。MIUI老版本一个路径新版一个路径到了HyperOS上又不一样。开发者选项里开关的名称也飘忽不定有的叫“蓝牙HCI信息收集日志”有的叫“Bluetooth HCI snoop log”你只看名字很难判断是不是同一个。所以我的习惯是不要背具体路径而是记住两条通用命令一条用来搜文件adb shell find /sdcard -iname *btsnoop* 2/dev/null另一条用来兜底直接进开发者选项找“日志抓取”功能生成的压缩包里面什么都有。这样不管系统怎么更新换代都能在几十秒内把日志搞到手。6.2 抓log的推荐习惯调试蓝牙问题尤其是做设备兼容性适配的时候我的建议是抓HCI log要像写实验记录一样规范。每一次抓包都记下时间、手机型号、系统版本、远端设备型号、操作步骤甚至把当时所处的环境区域也写上。因为蓝牙问题很多跟射频环境强相关同一个设备换个地方可能就复现不了。另外一个很实用的小习惯是抓完log之后不要急着删文件先用wireshark把几条关键过滤器的结果截图存档。这样下次遇到相似问题可以对比两次抓包中HCI事件序列的差异很多疑难杂症就是通过这种对比找到突破口的。在这几年的蓝牙开发里我越发觉得HCI log解析不只是协议栈工程师的技能做应用层的同学也该掌握。很多上层表现诡异的bug根因其实都在底层而HCI log是连接这两层认知的桥梁。在小米手机上把这个流程跑熟再去看其他品牌的Android手机也基本都是大同小异。以后你遇到蓝牙问题第一反应不是“改改代码试试”而是“先把HCI log抓出来看看”这个习惯养成以后调试效率会有质的提升。
返回列表