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 格式符:
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 打印关键数值,形成“谁调用 + 什么值”的完整调试信息,大幅提升问题定位效率。