实战 1:修改 BlueZ 源码,添加自定义开机日志

在蓝牙底层开发中,我们经常需要追踪 bluetoothd 的启动流程------它什么时候初始化了 D-Bus 连接?什么时候创建了 MGMT 套接字?什么时候加载了插件?原生日志机制虽然提供了 info()、DBG() 等函数,但默认情况下 DBG() 被关闭,info() 输出稀少,难以追踪完整启动链路。本文手把手教你修改 BlueZ 5.x 源码,在 bluetoothd 启动的关键节点添加自定义日志,完成从源码修改→编译→部署→验证的全流程实战,为后续深度二次开发打下基础。


一、BlueZ 启动流程 源码 全景

1.1 启动入口定位

bluetoothd 的入口函数位于 src/main.c第 1154 行的 main() 函数。完整启动流程如下:

cpp 复制代码
int main(int argc, char *argv[])
{
    /* ① 初始化默认配置 */
    init_defaults();                              // 第 1162 行

    /* ② 解析命令行参数 (-d/-p/-f/-E/-n 等) */
    context = g_option_context_new(NULL);
    g_option_context_add_main_entries(context, options, NULL);
    g_option_context_parse(context, &argc, &argv, &err);  // 第 1167 行

    /* ③ 初始化 backtrace 崩溃追踪 */
    btd_backtrace_init();                         // 第 1185 行

    /* ④ 初始化主循环 (GLib mainloop) */
    mainloop_init();                              // 第 1187 行

    /* ⑤ ★ 初始化日志系统 */
    __btd_log_init(option_debug, option_detach);  // 第 1189 行

    /* ⑥ 设置 GLib 日志重定向 */
    g_log_set_handler("GLib", ..., log_handler, NULL);  // 第 1191 行

    /* ⑦ systemd 状态通知 */
    mainloop_sd_notify("STATUS=Starting up");     // 第 1195 行

    /* ⑧ 加载配置文件 (/etc/bluetooth/main.conf) */
    main_conf = load_config(main_conf_file_path); // 第 1202 行
    parse_config(main_conf);                      // 第 1204 行

    /* ⑨ 连接 D-Bus 系统总线 */
    if (connect_dbus() < 0) {                     // 第 1206 行
        error("Unable to get on D-Bus");
        exit(1);
    }

    /* ⑩ 初始化适配器管理 (创建 MGMT 套接字) */
    if (adapter_init() < 0) {                     // 第 1216 行
        error("Adapter handling initialization failed");
        exit(1);
    }

    /* ⑪ 初始化设备/Agent/Profile 子系统 */
    btd_device_init();                            // 第 1221 行
    btd_agent_init();                             // 第 1222 行
    btd_profile_init();                           // 第 1223 行

    /* ⑫ 启动 SDP 服务器 (BR/EDR 模式) */
    start_sdp_server(sdp_mtu, sdp_flags);         // 第 1229 行

    /* ⑬ 加载插件 */
    plugin_init(option_plugin, option_noplugin);  // 第 1245 行

    /* ⑭ 初始化 rfkill */
    rfkill_init();                                // 第 1250 行

    /* ⑮ 进入主循环 */
    DBG("Entering main loop");                    // 第 1252 行
    mainloop_sd_notify("STATUS=Running");         // 第 1254 行
    mainloop_sd_notify("READY=1");                // 第 1255 行
    mainloop_run_with_signal(signal_callback, NULL);  // 第 1257 行

    /* ---- 以下为关闭流程 ---- */
    mainloop_sd_notify("STATUS=Quitting");
    plugin_cleanup();
    btd_profile_cleanup();
    btd_agent_cleanup();
    btd_device_cleanup();
    adapter_cleanup();
    rfkill_exit();
    stop_sdp_server();
    disconnect_dbus();
    info("Exit");                                 // 第 1282 行
    __btd_log_cleanup();
    return 0;
}

1.2 启动流程可视化

cpp 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                  bluetoothd 启动流程                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  main()                                                         │
│    │                                                            │
│    ├─① init_defaults()          设置默认配置                    │
│    │                                                            │
│    ├─② g_option_context_parse() 解析命令行参数                  │
│    │                                                            │
│    ├─③ btd_backtrace_init()     初始化崩溃追踪                  │
│    │                                                            │
│    ├─④ mainloop_init()          初始化 GLib 主循环              │
│    │                                                            │
│    ├─⑤ __btd_log_init()         ★ 初始化日志系统                │
│    │   └─ info("Bluetooth daemon %s", VERSION)                 │
│    │                                                            │
│    ├─⑥ g_log_set_handler()      GLib 日志重定向                 │
│    │                                                            │
│    ├─⑦ mainloop_sd_notify()     systemd STATUS=Starting up     │
│    │                                                            │
│    ├─⑧ load_config() + parse_config()  加载 /etc/bluetooth/main.conf │
│    │                                                            │
│    ├─⑨ connect_dbus()           连接 D-Bus 系统总线             │
│    │                                                            │
│    ├─⑩ adapter_init()           ★ 创建 MGMT 套接字             │
│    │   └─ mgmt_new_default()    → MGMT Socket                   │
│    │   └─ mgmt_send(READ_VERSION)                               │
│    │                                                            │
│    ├─⑪ btd_device_init()        设备管理初始化                  │
│    ├─⑪ btd_agent_init()         Agent 管理初始化                │
│    ├─⑪ btd_profile_init()       Profile 管理初始化              │
│    │                                                            │
│    ├─⑫ start_sdp_server()       SDP 服务器启动 (BR/EDR)         │
│    │                                                            │
│    ├─⑬ plugin_init()            加载内置/外部插件               │
│    │                                                            │
│    ├─⑭ rfkill_init()            rfkill 初始化                   │
│    │                                                            │
│    ├─⑮ mainloop_sd_notify()     STATUS=Running, READY=1        │
│    │                                                            │
│    └─ mainloop_run_with_signal()  ★ 进入主循环 (阻塞)          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

二、BlueZ 原生日志机制深度解析

2.1 日志系统架构

BlueZ 的日志系统定义在 src/log.c和 src/log.h中,由三部分组成:

2.2 日志函数源码

cpp 复制代码
/* src/log.h --- 宏定义 */

/* info: 始终输出,无文件名/函数名前缀 */
void info(const char *format, ...) __attribute__((format(printf, 1, 2)));

/* error/warn: 始终输出,自动添加文件名和函数名前缀 */
#define error(fmt, arg...) \
    btd_error(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg)
#define warn(fmt, arg...) \
    btd_warn(0xffff, "%s:%s() " fmt, __FILE__, __func__, ## arg)

/* DBG: 默认关闭,需 -d 参数开启,自动添加文件名和函数名前缀 */
#define DBG(fmt, arg...) DBG_IDX(0xffff, fmt, ## arg)

#define DBG_IDX(idx, fmt, arg...) do { \
    static struct btd_debug_desc __btd_debug_desc \
    __attribute__((used, section("__debug"), aligned(8))) = { \
        .file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \
    }; \
    if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \
        btd_debug(idx, "%s:%s() " fmt, __FILE__, __func__, ## arg); \
} while (0)
cpp 复制代码
/* src/log.c --- 函数实现 */

void info(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vsyslog(LOG_INFO, format, ap);     /* 输出到 syslog */
    va_end(ap);

    va_start(ap, format);
    monitor_log(HCI_DEV_NONE, LOG_INFO, format, ap);  /* 输出到 btmon */
    va_end(ap);
}

void btd_debug(uint16_t index, const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vsyslog(LOG_DEBUG, format, ap);
    va_end(ap);

    va_start(ap, format);
    monitor_log(index, LOG_DEBUG, format, ap);
    va_end(ap);
}

2.3 日志初始化

cpp 复制代码
/* src/log.c --- __btd_log_init() */

void __btd_log_init(const char *debug, int detach)
{
    int option = LOG_NDELAY | LOG_PID;

    /* 如果 -d 参数指定了调试过滤,按 ":" / "," / " " 分割 */
    if (debug != NULL)
        enabled = g_strsplit_set(debug, ":, ", 0);

    /* 启用匹配的 DBG 描述符 */
    __btd_enable_debug(__start___debug, __stop___debug);

    /* 打开 btmon 监控通道 */
    bt_log_open();

    /* -n (不 detach) 时同时输出到 stderr */
    if (!detach)
        option |= LOG_PERROR;

    openlog(LOG_IDENT, option, LOG_DAEMON);  /* LOG_IDENT = "bluetoothd" */

    info("Bluetooth daemon %s", VERSION);     /* ★ 第一条日志 */
}

2.4 原生日志的缺陷

|-----------------|-----------------------------------------------------------------|
| 缺陷 | 说明 |
| info() 输出稀少 | 整个启动过程只有 2 条 info()(开头 "Bluetooth daemon" + 结尾 "Exit"),无法追踪中间步骤 |
| DBG() 默认关闭 | 需要手动添加 -d 参数才输出,且输出到 syslog 的 DEBUG 级别可能被过滤 |
| 无启动耗时统计 | 没有时间打点,无法知道各阶段耗时 |
| 无环境信息 | 不打印 PID、工作目录、配置文件路径等运行环境信息 |
| 无阶段分隔 | 各子系统初始化日志混在一起,难以区分阶段 |

三、实战目标:添加三类自定义日志

我们将在 bluetoothd 启动流程中添加三类自定义日志:

|-------------|---------------------|-------------------------------------------------------|
| 类型 | 内容 | 插入位置 |
| 开机启动横幅 | 版本号、PID、编译时间、配置文件路径 | __btd_log_init()之后 |
| 设备初始化日志 | 各子系统初始化进度 | adapter_init() / btd_device_init() / plugin_init() 前后 |
| 启动状态打点 | 各阶段时间戳、总耗时 | 关键节点前后 |

四、手把手源码修改

4.1 修改 src/main.c --- 添加启动横幅与阶段日志

打开 src/main.c,在 main() 函数中添加自定义日志。

修改点 1:添加头文件

在 #include <stdio.h> 之后添加:

cpp 复制代码
#include <time.h>

修改点 2:添加启动横幅

在 __btd_log_init(option_debug, option_detach); 之后添加:

cpp 复制代码
    __btd_log_init(option_debug, option_detach);

    /* ===== 自定义开机日志:启动横幅 ===== */
    info("========================================");
    info("BlueZ Custom Build Starting...");
    info("  Version: %s", VERSION);
    info("  PID: %d", getpid());
    info("  Config file: %s", main_conf_file_path ? main_conf_file_path : "(default)");
    info("  Compiled: %s %s", __DATE__, __TIME__);
    info("========================================");

修改点 3:添加 D-Bus 连接日志

在 connect_dbus() 调用前后添加:

cpp 复制代码
    /* ===== 自定义启动日志:D-Bus 连接 ===== */
    info("[BOOT] Step 1: Connecting to D-Bus system bus...");
    if (connect_dbus() < 0) {
        error("Unable to get on D-Bus");
        exit(1);
    }
    info("[BOOT] Step 1: D-Bus connected OK");

修改点 4:添加适配器初始化日志

在 adapter_init() 调用前后添加:

cpp 复制代码
    /* ===== 自定义启动日志:适配器初始化 ===== */
    info("[BOOT] Step 2: Initializing adapter (MGMT socket)...");
    if (adapter_init() < 0) {
        error("Adapter handling initialization failed");
        exit(1);
    }
    info("[BOOT] Step 2: Adapter initialized OK");

修改点 5:添加子系统初始化日志

cpp 复制代码
    /* ===== 自定义启动日志:子系统初始化 ===== */
    info("[BOOT] Step 3: Initializing device manager...");
    btd_device_init();
    info("[BOOT] Step 3: Device manager OK");

    info("[BOOT] Step 4: Initializing agent manager...");
    btd_agent_init();
    info("[BOOT] Step 4: Agent manager OK");

    info("[BOOT] Step 5: Initializing profile manager...");
    btd_profile_init();
    info("[BOOT] Step 5: Profile manager OK");

修改点 6:添加插件加载日志(第 1245 行附近)

cpp 复制代码
    /* ===== 自定义启动日志:插件加载 ===== */
    info("[BOOT] Step 6: Loading plugins...");
    plugin_init(option_plugin, option_noplugin);
    info("[BOOT] Step 6: Plugins loaded OK");

修改点 7:添加主循环进入日志

cpp 复制代码
    /* ===== 自定义启动日志:进入主循环 ===== */
    info("[BOOT] Step 7: Entering main loop");
    info("[BOOT] BlueZ startup complete! All systems go.");
    DBG("Entering main loop");

    mainloop_sd_notify("STATUS=Running");
    mainloop_sd_notify("READY=1");

    mainloop_run_with_signal(signal_callback, NULL);

4.2 修改 src/adapter.c --- 添加设备初始化日志

打开 src/adapter.c,在 adapter_init() 函数中添加日志。

修改点:第 10335 行 adapter_init() 函数

cpp 复制代码
int adapter_init(void)
{
    dbus_conn = btd_get_dbus_connection();

    /* ===== 自定义日志:MGMT 套接字创建 ===== */
    info("[ADAPTER] Creating MGMT management socket...");

    mgmt_primary = mgmt_new_default();
    if (!mgmt_primary) {
        error("Failed to access management interface");
        return -EIO;
    }

    info("[ADAPTER] MGMT socket created successfully");

    if (getenv("MGMT_DEBUG"))
        mgmt_set_debug(mgmt_primary, mgmt_debug, "mgmt: ", NULL);

    DBG("sending read version command");

    info("[ADAPTER] Sending MGMT_OP_READ_VERSION...");

    if (mgmt_send(mgmt_primary, MGMT_OP_READ_VERSION,
                MGMT_INDEX_NONE, 0, NULL,
                read_version_complete, NULL, NULL) > 0) {
        info("[ADAPTER] MGMT version request sent, waiting for response...");
        return 0;
    }

    error("Failed to read management version information");
    return -EIO;
}

4.3 修改 src/plugin.c --- 添加插件加载详细日志

打开 src/plugin.c,在 plugin_init() 函数中添加日志。

修改点:第 102 行 plugin_init() 函数

在 DBG("Loading builtin plugins"); 之后添加:

cpp 复制代码
    DBG("Loading builtin plugins");

    /* ===== 自定义日志:插件加载详情 ===== */
    info("[PLUGIN] Starting plugin initialization");
    info("[PLUGIN] Enabled filter: %s", enable ? enable : "(all)");
    info("[PLUGIN] Disabled filter: %s", disable ? disable : "(none)");

    for (i = 0; __bluetooth_builtin[i]; i++) {
        if (!enable_plugin(__bluetooth_builtin[i]->name, cli_enabled,
                                cli_disabled))
            continue;

        info("[PLUGIN] Loading builtin plugin: %s", __bluetooth_builtin[i]->name);
        add_plugin(NULL,  __bluetooth_builtin[i]);
    }

4.4 修改 src/log.c --- 添加启动耗时打点

打开 src/log.c,在 __btd_log_init() 中添加启动时间记录。

修改点:在文件头部添加全局变量

在 #define LOG_IDENT "bluetoothd" 之后添加:

cpp 复制代码
/* ===== 自定义:启动时间打点 ===== */
static struct timespec boot_start_time;

修改点:在 __btd_log_init() 中记录启动开始时间

cpp 复制代码
void __btd_log_init(const char *debug, int detach)
{
    int option = LOG_NDELAY | LOG_PID;

    /* ===== 自定义:记录启动开始时间 ===== */
    clock_gettime(CLOCK_MONOTONIC, &boot_start_time);

    if (debug != NULL)
        enabled = g_strsplit_set(debug, ":, ", 0);

    __btd_enable_debug(__start___debug, __stop___debug);

    bt_log_open();

    if (!detach)
        option |= LOG_PERROR;

    openlog(LOG_IDENT, option, LOG_DAEMON);

    info("Bluetooth daemon %s", VERSION);
    info("[BOOT] Log system initialized at monotonic %ld.%09ld",
         boot_start_time.tv_sec, boot_start_time.tv_nsec);
}

4.5 修改汇总

|-------------------|----------|----------------|
| 文件 | 修改行数 | 修改内容 |
| src/main.c | +25 行 | 启动横幅 + 7 步阶段日志 |
| src/adapter.c | +6 行 | MGMT 套接字创建日志 |
| src/plugin.c | +4 行 | 插件加载详情日志 |
| src/log.c | +6 行 | 启动时间打点 |

五、编译配置与构建

5.1 编译环境准备

cpp 复制代码
# Ubuntu/Debian 安装编译依赖
sudo apt-get update
sudo apt-get install -y \
    build-essential \
    libglib2.0-dev \
    libdbus-1-dev \
    libudev-dev \
    libical-dev \
    readline-common \
    autoconf \
    automake \
    libtool \
    pkg-config \
    bison \
    flex \
    git

5.2 配置编译选项

cpp 复制代码
cd bluez_source

# 方式一:最小化配置(只编译 bluetoothd,不含插件)
./configure --prefix=/usr \
    --mandir=/usr/share/man \
    --sysconfdir=/etc \
    --localstatedir=/var \
    --enable-library \
    --disable-systemd \
    --disable-manpages \
    --disable-cups \
    --disable-mesh \
    --disable-btpclient \
    --disable-hid2hci \
    --disable-a2dp \
    --disable-avrcp

# 方式二:完整配置(推荐开发使用)
./configure --prefix=/usr \
    --mandir=/usr/share/man \
    --sysconfdir=/etc \
    --localstatedir=/var \
    --enable-library \
    --enable-debug \
    --enable-testing \
    --enable-experimental

5.3 编译

cpp 复制代码
# 编译(-j 加速)
make -j$(nproc)

# 仅编译 bluetoothd(快速验证修改)
make -j$(nproc) src/bluetoothd

5.4 编译后验证

cpp 复制代码
# 验证编译产物
ls -la src/bluetoothd
file src/bluetoothd
# 输出: ELF 64-bit LSB shared object, x86-64, dynamically linked

# 验证版本号
./src/bluetoothd --version
# 输出: 5.64

# 验证自定义日志(前台模式运行)
sudo ./src/bluetoothd -n -d

预期输出:

cpp 复制代码
bluetoothd[12345]: Bluetooth daemon 5.64
bluetoothd[12345]: [BOOT] Log system initialized at monotonic 12345.678901234
bluetoothd[12345]: ========================
bluetoothd[12345]: BlueZ Custom Build Starting...
bluetoothd[12345]:   Version: 5.64
bluetoothd[12345]:   PID: 12345
bluetoothd[12345]:   Config file: /etc/bluetooth/main.conf
bluetoothd[12345]:   Compiled: Aug  2 2026 14:30:00
bluetoothd[12345]: ========================
bluetoothd[12345]: [BOOT] Step 1: Connecting to D-Bus system bus...
bluetoothd[12345]: [BOOT] Step 1: D-Bus connected OK
bluetoothd[12345]: [BOOT] Step 2: Initializing adapter (MGMT socket)...
bluetoothd[12345]: [ADAPTER] Creating MGMT management socket...
bluetoothd[12345]: [ADAPTER] MGMT socket created successfully
bluetoothd[12345]: [ADAPTER] Sending MGMT_OP_READ_VERSION...
bluetoothd[12345]: [ADAPTER] MGMT version request sent, waiting for response...
bluetoothd[12345]: [BOOT] Step 2: Adapter initialized OK
bluetoothd[12345]: [BOOT] Step 3: Initializing device manager...
bluetoothd[12345]: [BOOT] Step 3: Device manager OK
bluetoothd[12345]: [BOOT] Step 4: Initializing agent manager...
bluetoothd[12345]: [BOOT] Step 4: Agent manager OK
bluetoothd[12345]: [BOOT] Step 5: Initializing profile manager...
bluetoothd[12345]: [BOOT] Step 5: Profile manager OK
bluetoothd[12345]: [BOOT] Step 6: Loading plugins...
bluetoothd[12345]: [PLUGIN] Starting plugin initialization
bluetoothd[12345]: [PLUGIN] Enabled filter: (all)
bluetoothd[12345]: [PLUGIN] Disabled filter: (none)
bluetoothd[12345]: [PLUGIN] Loading builtin plugin: hostname
bluetoothd[12345]: [PLUGIN] Loading builtin plugin: autopair
bluetoothd[12345]: [PLUGIN] Loading builtin plugin: policy
bluetoothd[12345]: [BOOT] Step 6: Plugins loaded OK
bluetoothd[12345]: [BOOT] Step 7: Entering main loop
bluetoothd[12345]: [BOOT] BlueZ startup complete! All systems go.

六、环境部署与替换

6.1 停止系统 bluetoothd

cpp 复制代码
# 停止系统服务
sudo systemctl stop bluetooth

# 确认已停止
sudo systemctl status bluetooth
# Active: inactive (dead)

6.2 备份原始 bluetoothd

cpp 复制代码
# 查找系统 bluetoothd 路径
which bluetoothd
# 通常在: /usr/libexec/bluetooth/bluetoothd

# 备份
sudo cp /usr/libexec/bluetooth/bluetoothd \
        /usr/libexec/bluetooth/bluetoothd.orig

6.3 替换 bluetoothd

cpp 复制代码
# 复制编译产物
sudo cp src/bluetoothd /usr/libexec/bluetooth/bluetoothd

# 确保权限正确
sudo chmod 755 /usr/libexec/bluetooth/bluetoothd
sudo chown root:root /usr/libexec/bluetooth/bluetoothd

6.4 安装共享库(如需要)

如果 bluetoothd 依赖的库也有修改,需同步安装:

cpp 复制代码
# 安装到系统路径
sudo make install

# 或仅安装库文件
sudo cp lib/.libs/libbluetooth.so* /usr/lib/
sudo ldconfig

6.5 恢复系统服务

cpp 复制代码
# 启动服务
sudo systemctl start bluetooth

# 检查状态
sudo systemctl status bluetooth

6.6 验证日志输出

cpp 复制代码
# 方法 1:直接查看 syslog
sudo journalctl -u bluetooth -f --no-pager

# 方法 2:查看 syslog 文件
tail -f /var/log/syslog | grep bluetoothd

# 方法 3:前台调试模式
sudo /usr/libexec/bluetooth/bluetoothd -n -d

七、常见问题排查

7.1 编译报错

问题 1:config.h 找不到

cpp 复制代码
configure: error: cannot find config.h

原因:未运行 ./configure 或配置失败。

解决:

cpp 复制代码
# 清理后重新配置
make distclean
./configure --prefix=/usr --enable-library
make -j$(nproc)

问题 2:缺少 glib.h

cpp 复制代码
fatal error: glib.h: No such file or directory

解决:

cpp 复制代码
sudo apt-get install libglib2.0-dev

问题 3:缺少 dbus/dbus.h

cpp 复制代码
fatal error: dbus/dbus.h: No such file or directory

解决:

cpp 复制代码
sudo apt-get install libdbus-1-dev

问题 4:DATE 和 TIME 警告

cpp 复制代码
warning: macro "__DATE__" might prevent reproducible builds

说明:这是警告而非错误,可忽略。如需消除,在 Makefile.am 中添加 `-Wno-date-time`。

7.2 日志不输出

问题:运行 bluetoothd 看不到自定义日志

排查步骤:

cpp 复制代码
# 1. 确认运行的是修改后的 bluetoothd
md5sum /usr/libexec/bluetooth/bluetoothd
md5sum src/bluetoothd
# 两个 MD5 应一致

# 2. 确认 bluetoothd 正在运行
ps aux | grep bluetoothd

# 3. 检查 syslog 配置是否过滤了 INFO 级别
grep -i bluetooth /etc/rsyslog.conf
grep -i bluetooth /etc/rsyslog.d/*.conf

# 4. 使用前台模式直接观察输出
sudo /usr/libexec/bluetooth/bluetoothd -n -d
# -n: 不 detach (前台运行)
# -d: 开启 DBG 调试日志

问题:syslog 中没有 bluetoothd 日志

原因:syslog 可能未配置捕获 LOG_DAEMON facility。

解决:

cpp 复制代码
# 检查 syslog 配置
cat /etc/rsyslog.conf | grep -i daemon

# 或直接使用 journalctl(systemd 系统)
sudo journalctl -t bluetoothd -f

7.3 权限问题

问题:bluetoothd 启动失败,提示权限不足

cpp 复制代码
Failed to access management interface

原因:MGMT Socket 需要 root 权限或 CAP_NET_ADMIN 能力。

解决:

cpp 复制代码
# 方法 1:以 root 运行
sudo ./src/bluetoothd -n -d

# 方法 2:设置 capabilities
sudo setcap cap_net_admin,cap_net_raw+eip ./src/bluetoothd
./src/bluetoothd -n -d

# 方法 3:通过 systemd 运行(自动处理权限)
sudo systemctl start bluetooth

7.4 运行时冲突

问题:bluetoothd 启动后立即退出

cpp 复制代码
bluetoothd[12345]: D-Bus setup failed: Name already in use

原因:另一个 bluetoothd 实例已在运行。

解决:

cpp 复制代码
# 查找并杀掉旧进程
sudo pkill bluetoothd
# 或
sudo systemctl stop bluetooth

# 确认无残留进程
ps aux | grep bluetoothd

# 重新启动
sudo ./src/bluetoothd -n -d

7.5 配置文件问题

问题:配置文件路径不对

cpp 复制代码
bluetoothd[12345]: Parsing /etc/bluetooth/main.conf failed: No such file

解决:

cpp 复制代码
# 方式 1:使用 -f 指定配置文件
sudo ./src/bluetoothd -n -d -f /path/to/main.conf

# 方式 2:创建默认配置文件
sudo mkdir -p /etc/bluetooth
sudo cp src/main.conf /etc/bluetooth/main.conf

# 方式 3:不使用配置文件(使用默认值)
# 不指定 -f,bluetoothd 会尝试 /etc/bluetooth/main.conf,找不到就用默认值

八、恢复原始 bluetoothd

如需恢复到原始版本:

cpp 复制代码
# 停止服务
sudo systemctl stop bluetooth

# 恢复备份
sudo cp /usr/libexec/bluetooth/bluetoothd.orig \
        /usr/libexec/bluetooth/bluetoothd

# 重启服务
sudo systemctl start bluetooth

# 验证
/usr/libexec/bluetooth/bluetoothd --version

九、自定义日志扩展方法

9.1 添加带时间戳的耗时打点

在 src/log.h中添加辅助宏:

cpp 复制代码
/* 在文件末尾添加 */

#include <time.h>

/* 耗时打点宏 */
#define BOOT_TIMESTAMP(label) do { \
    struct timespec _ts; \
    clock_gettime(CLOCK_MONOTONIC, &_ts); \
    info("[TIMESTAMP] %s: %ld.%09ld", label, _ts.tv_sec, _ts.tv_nsec); \
} while (0)

/* 耗时统计宏(自动计算与开始时间的差值) */
#define BOOT_ELAPSED(label, start) do { \
    struct timespec _ts; \
    clock_gettime(CLOCK_MONOTONIC, &_ts); \
    double _elapsed = (_ts.tv_sec - start.tv_sec) + \
                      (_ts.tv_nsec - start.tv_nsec) / 1e9; \
    info("[ELAPSED] %s: %.6f sec", label, _elapsed); \
} while (0)

在 main() 中使用:

cpp 复制代码
    struct timespec boot_start;

    __btd_log_init(option_debug, option_detach);
    clock_gettime(CLOCK_MONOTONIC, &boot_start);
    BOOT_TIMESTAMP("log_init_done");

    /* ... */

    BOOT_TIMESTAMP("before_dbus");
    connect_dbus();
    BOOT_ELAPSED("dbus_connect", boot_start);

    /* ... */

    BOOT_TIMESTAMP("before_adapter");
    adapter_init();
    BOOT_ELAPSED("adapter_init", boot_start);

    /* ... */

    BOOT_TIMESTAMP("before_mainloop");
    BOOT_ELAPSED("total_startup", boot_start);

9.2 添加条件日志开关

在 src/main.c中添加环境变量控制:

cpp 复制代码
/* 在 main() 开头添加 */
static gboolean boot_log_enabled = FALSE;

/* 检查环境变量 */
if (getenv("BLUEZ_BOOT_LOG"))
    boot_log_enabled = TRUE;

/* 使用条件日志 */
if (boot_log_enabled)
    info("[BOOT] Connecting to D-Bus...");

connect_dbus();

if (boot_log_enabled)
    info("[BOOT] D-Bus connected OK");

运行时通过环境变量控制:

cpp 复制代码
# 开启启动日志
sudo BLUEZ_BOOT_LOG=1 ./src/bluetoothd -n

# 不开启(正常模式)
sudo ./src/bluetoothd -n

9.3 添加日志输出到文件

如果需要将自定义日志同时输出到独立文件:

cpp 复制代码
/* 在 __btd_log_init() 中添加 */
static FILE *boot_log_fp = NULL;

void __btd_log_init(const char *debug, int detach)
{
    /* ... 原有代码 ... */

    /* 自定义:打开启动日志文件 */
    boot_log_fp = fopen("/var/log/bluetoothd-boot.log", "a");
    if (boot_log_fp) {
        fprintf(boot_log_fp, "\n=== bluetoothd boot %s ===\n", VERSION);
        fflush(boot_log_fp);
    }
}

/* 自定义:带文件输出的 info */
#define boot_info(fmt, arg...) do { \
    info(fmt, ## arg); \
    if (boot_log_fp) { \
        fprintf(boot_log_fp, fmt "\n", ## arg); \
        fflush(boot_log_fp); \
    } \
} while (0)

9.4 在其他源码文件中添加日志

BlueZ 的日志宏在所有源码文件中都可用(通过 #include "log.h")。例如在设备连接流程中添加日志:

cpp 复制代码
/* src/device.c --- 在设备连接函数中添加 */
/* 找到 device_connect_le() 或类似的连接函数 */

static int device_connect_le(struct btd_device *device)
{
    /* 原有代码... */

    info("[DEVICE] LE connect request: %s", addr);

    /* 原有代码... */

    info("[DEVICE] LE connect sent, waiting for response");

    /* 原有代码... */
}

十、源码二次开发通用方法总结

10.1 定位入口的通用方法

|------------|-----------------------------------------------------|
| | |
| 守护进程入口 | grep -n "^int main" src/main.c |
| 子系统初始化 | grep -rn "_init\b" src/*.c |
| D-Bus 方法实现 | grep -rn "method_call|method_func" src/*.c |
| MGMT 命令处理 | grep -rn "mgmt_send|mgmt_register" src/*.c |
| 事件回调 | grep -rn "_cb\b|_callback\b|_complete\b" src/*.c |
| 配置解析 | grep -rn "g_key_file_get|parse_config" src/main.c |

10.2 修改源码的通用步骤

cpp 复制代码
1. 阅读源码 → 理解函数调用链
2. 定位插入点 → 找到关键函数的入口/出口
3. 添加日志 → 使用 info()/DBG()/error() 宏
4. 编译验证 → make -j$(nproc) src/bluetoothd
5. 前台测试 → sudo ./src/bluetoothd -n -d
6. 部署替换 → cp + systemctl restart
7. 日志验证 → journalctl -u bluetooth -f

10.3 日志宏使用指南

|----------------------|----------|----------|-----------------------------------|
| 宏 | 使用场景 | 输出条件 | 输出格式 |
| info(fmt, ...) | 关键状态信息 | 始终输出 | bluetoothd[PID]: 消息 |
| warn(fmt, ...) | 警告信息 | 始终输出 | bluetoothd[PID]: file:func() 消息 |
| error(fmt, ...) | 错误信息 | 始终输出 | bluetoothd[PID]: file:func() 消息 |
| DBG(fmt, ...) | 调试信息 | -d 开启 | bluetoothd[PID]: file:func() 消息 |
| btd_info(idx, ...) | 带设备索引 | 始终输出 | bluetoothd[PID]: 消息 |

10.4 调试运行参数速查

cpp 复制代码
# 前台运行 + 全部调试日志
sudo ./src/bluetoothd -n -d

# 前台运行 + 指定模块调试
sudo ./src/bluetoothd -n -d src/adapter.c:src/device.c

# 前台运行 + 指定配置文件
sudo ./src/bluetoothd -n -d -f /etc/bluetooth/main.conf

# 前台运行 + 实验性特性
sudo ./src/bluetoothd -n -d -E

# 前台运行 + 兼容模式
sudo ./src/bluetoothd -n -d -C

# 查看 MGMT 协议交互
sudo MGMT_DEBUG=1 ./src/bluetoothd -n -d

十一、总结

11.1 本文完成的工作

  1. 定位了 bluetoothd 启动入口:src/main.c 的 main() 函数,15 步启动流程

  2. 解析了 BlueZ 日志机制:info()/DBG()/error()/warn()宏 → vsyslog() + monitor_log() 双通道输出

  3. 添加了三类自定义日志:

  • 启动横幅(版本/PID/配置文件/编译时间)

  • 设备初始化日志(D-Bus/适配器/设备/Agent/Profile/插件 7 步)

  • 启动状态打点(时间戳记录)

  1. 完成了全流程实战:源码修改 → ./configure → make → 部署替换 → 日志验证

  2. 解决了 5 类常见问题:编译报错、日志不输出、权限不足、运行时冲突、配置文件

11.2 关键经验

  • info() 始终输出,适合用于自定义启动日志;DBG() 需要 -d 参数,适合调试细节

  • -n -d 是开发调试的标配参数:`-n` 前台运行 + LOG_PERROR(输出到 stderr),`-d` 开启所有 DBG()日志

  • journalctl -u bluetooth -f 是查看日志的标准方式(systemd 系统)

  • 修改后必须 `make` 重新编译,然后替换 `/usr/libexec/bluetooth/bluetoothd`

  • 始终备份原始 `bluetoothd`:`cp bluetoothd bluetoothd.orig`

11.3 扩展方向

掌握了本文的源码修改方法后,可以进一步:

  • 在设备配对流程中添加日志(`src/adapter.c` 的 `pair_device` 回调链)

  • 在GATT 读写流程中添加日志(`src/gatt-database.c` / `src/gatt-client.c`)

  • 在连接管理流程中添加日志(`src/device.c` 的 `device_connect_le` / `device_connect_bredr`)

  • 添加性能统计(各阶段耗时、消息计数)

  • 添加自定义 MGMT 命令(扩展 MGMT 协议用于私有功能调试)


附录:核心源码文件索引

|-------------------|----------------------------------------------------------|
| 文件 | 关键内容 |
| src/main.c | main() 入口、启动流程、配置解析、命令行参数 |
| src/log.c | info()/btd_log()/btd_error()/__btd_log_init() 实现 |
| src/log.h | info/DBG/error/warn 宏定义、btd_debug_desc 结构 |
| src/adapter.c | adapter_init() MGMT 套接字创建、适配器管理 |
| src/plugin.c | plugin_init() 内置/外部插件加载 |
| src/device.c | btd_device_init() 设备管理初始化 |
| Makefile.am | 编译规则:src_bluetoothd_SOURCES / src_bluetoothd_LDADD |
| configure.ac | 版本号 AC_INIT(bluez, 5.64)、编译选项配置 |

参考文献

  • BlueZ Source Code: src/main.c, src/log.c, src/log.h

  • Linux syslog: man 3 syslog, man 3 vsyslog

  • GLib Main Loop: man g_main_loop_run

  • systemd Journal: man journalctl


相关推荐
叼烟扛炮28 分钟前
Linux 学习笔记(五十):网络层——IP 协议、子网划分与 NAT 魔法
linux·笔记·学习
叼烟扛炮34 分钟前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法
AR-26710-1 小时前
Linux Day32——实操五类资源告急排查
linux·运维
StarsGreen1 小时前
libva中的图像格式说明
linux
H.莓飛2 小时前
【C++】【STL】vector_OJ题
linux·服务器·开发语言·数据结构·c++·visual studio
Rabitebla2 小时前
【Linux操作系统】进程篇(十一):进程程序替换——从底层原理到 exec 函数族
linux·服务器·c++·笔记·学习·算法
无人生还别怕3 小时前
如何搭建docker仓库(轻量级)registry
linux·运维·docker·云原生·容器·registry·openanolisos
无足鸟ICT4 小时前
【RHCA+】agedu命令(查看磁盘使用情况)
linux
StarsGreen4 小时前
Android 启动流程详解
android·linux·单片机·嵌入式硬件