4. printk 格式说明符:%d、%x、%p、%s、%pf 等常用格式符、打印指针与函数名

在 Android 内核调试中,printk 是最基础也是最常用的日志输出手段。掌握其格式说明符(Format Specifier)的用法,能够让你在调试时快速、准确地获取关键信息。本节将系统梳理 printk 的常用格式符,并重点讲解指针与函数名的打印技巧。

4.1 基础数值格式符

内核调试中最常处理的数据类型包括整数、十六进制数、字符串等。下表列出了最常用的基础格式符及其适用场景:

格式符 数据类型 示例输出 典型调试场景
%d int 42 打印计数器、状态码、索引值
%u unsigned int 100 打印无符号长度、标志位
%x unsigned int ff 打印寄存器值、错误码、掩码
%lx unsigned long ffff0000 打印物理地址、大范围位图
%ld long -12345 打印有符号长整型差值
%s char * hello 打印设备名称、路径、错误描述
%c char A 打印单个字符或 ASCII 码
调试建议:在 Android 内核驱动中,%x 常用于打印 errno 或 regmap 寄存器值。注意 %d 与 %u 的区别,避免符号扩展导致数值误解。

4.2 指针打印格式符:%p 家族

指针打印是内核调试的核心需求。Linux 内核为 %p 扩展了丰富的子格式符,用于打印不同类型的指针信息,同时兼顾安全性(如哈希指针)。

格式符 含义 示例输出 说明
%p 普通指针 00000000deadbeef 默认打印完整地址(可能被哈希)
%px 未哈希指针 ffff000012345678 打印真实地址,需开启 kptr_restrict
%pK 内核指针 0000000012345678 根据 kptr_restrict 决定是否哈希
%pS 符号化指针 do_irq+0x10/0x200 打印函数名+偏移量,最常用
%pF 函数指针 my_driver_probe 打印函数名(已弃用,推荐 %pS)
%pI4 IPv4 地址 192.168.1.1 打印网络地址
%pM MAC 地址 00:11:22:33:44:55 打印以太网 MAC
安全注意:在 Android 生产内核中,%p 默认输出哈希值,防止地址泄露。调试时可通过 echo 0 > /proc/sys/kernel/kptr_restrict 临时关闭哈希,但切勿在用户设备上长期开启。

4.3 打印函数名与调用栈

在驱动调试中,定位代码执行路径至关重要。%pS 和 %pB 是打印函数名和回溯信息的利器。

4.3.1 使用 %pS 打印当前函数名

#include <linux/kernel.h>

void my_function(void)
{
    printk(KERN_INFO "Entering %pS\n", __builtin_return_address(0));
    // 或者使用 __func__ 预定义宏
    printk(KERN_INFO "Current function: %s\n", __func__);
}

4.3.2 打印调用栈回溯

#include <linux/kernel.h>

void dump_callstack(void)
{
    printk(KERN_ERR "Call stack:\n");
    show_stack(NULL, NULL);  // 内核提供的栈回溯函数
}
实战技巧:在驱动 probe 函数中插入 printk("%pS\n", __builtin_return_address(0)),可以快速确认调用链,排查 probe 被谁触发。

4.4 格式符组合与高级用法

实际调试中,经常需要组合多个格式符输出结构化信息。以下是一个典型的驱动调试日志示例:

struct device *dev;
int ret;
void *buf;

printk(KERN_ERR
    "my_driver: dev=%p, ret=%d, buf=%px, func=%pS\n",
    dev, ret, buf, __builtin_return_address(0));

输出示例:

my_driver: dev=00000000deadbeef, ret=-22, buf=ffff000012345678, func=my_probe+0x50/0x100

4.5 格式符选择流程图

下面的 SVG 流程图展示了在调试时如何根据数据类型选择合适的 printk 格式符:

printk 格式符选择决策流程 需要打印什么? 数值类型 %d / %u / %x / %lx 指针类型 %p / %px / %pK %pS / %pF / %pB 字符串类型 %s / %c %pI4 / %pM / %pU printk(KERN_INFO "...", var); 组合使用,如 "ret=%d, ptr=%pS" 提示:%pS 是调试函数调用链的首选格式符 结合 __builtin_return_address(0) 可打印调用者函数名

4.6 常见调试场景与格式符对照

下表总结了 Android 内核驱动调试中的典型场景及其推荐的格式符组合:

调试场景 推荐格式符 示例代码
打印设备结构体指针 %p printk("dev=%p\n", dev);
打印函数调用链 %pS printk("caller=%pS\n", __builtin_return_address(0));
打印寄存器值 %x 或 %lx printk("reg=0x%x\n", readl(base));
打印错误码 %d printk("err=%d\n", PTR_ERR(ptr));
打印设备名称 %s printk("name=%s\n", dev_name(dev));
打印 DMA 地址 %pad printk("dma_addr=%pad\n", &dma_addr);
内核调试黄金法则:优先使用 %pS 打印函数名,配合 %d 和 %x 打印关键数值,形成“谁调用 + 什么值”的完整调试信息,大幅提升问题定位效率。