如何正确使用 printk 格式说明符¶
- 作者:
Randy Dunlap <rdunlap@infradead.org>
- 作者:
Andrew Murray <amurray@mpc-data.co.uk>
整数类型¶
If variable is of Type, use printk format specifier:
------------------------------------------------------------
signed char %d or %hhx
unsigned char %u or %x
char %u or %x
short int %d or %hx
unsigned short int %u or %x
int %d or %x
unsigned int %u or %x
long %ld or %lx
unsigned long %lu or %lx
long long %lld or %llx
unsigned long long %llu or %llx
size_t %zu or %zx
ssize_t %zd or %zx
s8 %d or %hhx
u8 %u or %x
s16 %d or %hx
u16 %u or %x
s32 %d or %x
u32 %u or %x
s64 %lld or %llx
u64 %llu or %llx
如果
示例
printk("test: latency: %llu cycles\n", (unsigned long long)time);
提醒:sizeof() 返回 size_t 类型。
内核的 printf 不支持 %n。浮点格式(%e, %f, %g, %a)也无法识别,原因显而易见。使用任何不支持的说明符或长度修饰符都会导致 WARN 警告并从 vsnprintf()
中提前返回。
指针类型¶
原始指针值可以使用 %p 打印,它会在打印前对地址进行哈希处理。内核还支持扩展说明符来打印不同类型的指针。
一些扩展说明符会打印给定地址上的数据,而不是打印地址本身。在这种情况下,可能会打印以下错误消息而不是无法访问的信息
(null) data on plain NULL address
(efault) data on invalid address
(einval) invalid data on a valid address
普通指针¶
%p abcdef12 or 00000000abcdef12
不带说明符扩展(即不加修饰的 %p)打印的指针会经过哈希处理,以防止泄露内核内存布局信息。这还具有提供唯一标识符的额外好处。在 64 位机器上,前 32 位将被清零。内核将打印 (ptrval)
直到收集到足够的熵。
在可能的情况下,使用 %pS 或 %pB(如下所述)等专用修饰符,以避免提供必须事后解释的未哈希地址。如果不可能,并且打印地址的目的是为了提供更多调试信息,则在调试期间使用 %p 并通过 no_hash_pointers
参数启动内核,这将打印所有 %p 地址而无需修改。如果您 确实 总是希望获取未修改的地址,请参阅下面的 %px。
如果(并且仅当)您在 procfs 或 sysfs 等虚拟文件中打印地址内容(使用例如 seq_printf(),而不是 printk()
),并且这些内容由用户空间进程读取,请使用下面描述的 %pK 修饰符代替 %p 或 %px。
错误指针¶
%pe -ENOSPC
用于将错误指针(即 IS_ERR()
为真的指针)打印为符号错误名称。对于没有已知符号名称的错误值,将以十进制打印,而作为 %pe 参数传递的非 ERR_PTR 则被视为普通 %p。
符号/函数指针¶
%pS versatile_init+0x0/0x110
%ps versatile_init
%pSR versatile_init+0x9/0x110
(with __builtin_extract_return_addr() translation)
%pB prev_fn_of_versatile_init+0x88/0x88
S
和 s
说明符用于以符号格式打印指针。它们会生成带偏移量 (S) 或不带偏移量 (s) 的符号名称。如果 KALLSYMS 被禁用,则会打印符号地址。
B
说明符会生成带偏移量的符号名称,并应在打印堆栈回溯时使用。该说明符会考虑在使用尾调用并标记有 noreturn GCC 属性时可能发生的编译器优化效果。
如果指针位于模块内,则在符号名称后打印模块名称以及可选的构建 ID,并在说明符末尾附加一个额外的 b
。
%pS versatile_init+0x0/0x110 [module_name]
%pSb versatile_init+0x0/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
%pSRb versatile_init+0x9/0x110 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
(with __builtin_extract_return_addr() translation)
%pBb prev_fn_of_versatile_init+0x88/0x88 [module_name ed5019fdf5e53be37cb1ba7899292d7e143b259e]
来自 BPF / 跟踪的探测指针¶
%pks kernel string
%pus user string
k
和 u
说明符用于打印先前从内核内存 (k) 或用户内存 (u) 中探测到的内存。随后的 s
说明符用于打印字符串。对于在常规 vsnprintf()
中的直接使用,(k) 和 (u) 注释会被忽略,但是,例如在 BPF 的 bpf_trace_printk() 之外使用时,它会读取其指向的内存而不会发生故障。
内核指针¶
%pK 01234567 or 0123456789abcdef
用于打印应向非特权用户隐藏的内核指针。%pK 的行为取决于 kptr_restrict sysctl — 更多详细信息请参阅/proc/sys/kernel/ 的文档。
此修饰符仅用于生成由用户空间从 procfs 或 sysfs 等读取的文件内容,而不是用于 dmesg。关于如何在 printk()
中管理哈希指针的讨论,请参阅上面关于 %p 的部分。
未修改的地址¶
%px 01234567 or 0123456789abcdef
当您确实想要打印地址时,用于打印指针。在用 %px 打印指针之前,请考虑是否会泄露有关内核内存布局的敏感信息。%px 在功能上等同于 %lx(或 %lu)。%px 更受青睐,因为它更容易进行唯一 grep。如果将来我们需要修改内核处理指针打印的方式,我们将能够更好地找到调用点。
在使用 %px 之前,请考虑在调试会话期间,%p 结合启用 no_hash_pointers
内核参数是否足够(请参阅上面 %p 的描述)。%px 的一个有效场景可能是在 panic 之前立即打印信息,这无论如何都能防止任何敏感信息被利用,并且使用 %px 就无需在没有 no_hash_pointers 的情况下重现 panic。
指针差异¶
%td 2560
%tx a00
要打印指针差异,请对 ptrdiff_t 使用 %t 修饰符。
示例
printk("test: difference between pointers: %td\n", ptr2 - ptr1);
结构体资源¶
%pr [mem 0x60000000-0x6fffffff flags 0x2200] or
[mem 0x60000000 flags 0x2200] or
[mem 0x0000000060000000-0x000000006fffffff flags 0x2200]
[mem 0x0000000060000000 flags 0x2200]
%pR [mem 0x60000000-0x6fffffff pref] or
[mem 0x60000000 pref] or
[mem 0x0000000060000000-0x000000006fffffff pref]
[mem 0x0000000060000000 pref]
用于打印结构体资源。R
和 r
说明符会打印带(R)或不带(r)解码标志成员的资源。如果 start 等于 end,则只打印 start 值。
通过引用传递。
物理地址类型 phys_addr_t¶
%pa[p] 0x01234567 or 0x0123456789abcdef
用于打印 phys_addr_t 类型(及其派生类型,例如 resource_size_t),其宽度可能因构建选项而异,无论 CPU 数据路径的宽度如何。
通过引用传递。
结构体范围¶
%pra [range 0x0000000060000000-0x000000006fffffff] or
[range 0x0000000060000000]
用于打印 struct range。struct range 存储任意范围的 u64 值。如果 start 等于 end,则只打印 start 值。
通过引用传递。
DMA 地址类型 dma_addr_t¶
%pad 0x01234567 or 0x0123456789abcdef
用于打印 dma_addr_t 类型,其宽度可能因构建选项而异,无论 CPU 数据路径的宽度如何。
通过引用传递。
原始缓冲区作为转义字符串¶
%*pE[achnops]
用于将原始缓冲区打印为转义字符串。对于以下缓冲区
1b 62 20 5c 43 07 22 90 0d 5d
一些例子展示了如何进行转换(不包括周围的引号)
%*pE "\eb \C\a"\220\r]"
%*pEhp "\x1bb \C\x07"\x90\x0d]"
%*pEa "\e\142\040\\\103\a\042\220\r\135"
转换规则根据可选的标志组合应用(详细信息请参阅 string_escape_mem()
内核文档)
a - ESCAPE_ANY
c - ESCAPE_SPECIAL
h - ESCAPE_HEX
n - ESCAPE_NULL
o - ESCAPE_OCTAL
p - ESCAPE_NP
s - ESCAPE_SPACE
默认使用 ESCAPE_ANY_NP。
ESCAPE_ANY_NP 在许多情况下都是一个明智的选择,特别是对于打印 SSID。
如果省略字段宽度,则只转义 1 字节。
原始缓冲区作为十六进制字符串¶
%*ph 00 01 02 ... 3f
%*phC 00:01:02: ... :3f
%*phD 00-01-02- ... -3f
%*phN 000102 ... 3f
用于将小缓冲区(最长 64 字节)作为带特定分隔符的十六进制字符串打印。对于更大的缓冲区,请考虑使用 print_hex_dump()
。
MAC/FDDI 地址¶
%pM 00:01:02:03:04:05
%pMR 05:04:03:02:01:00
%pMF 00-01-02-03-04-05
%pm 000102030405
%pmR 050403020100
用于以十六进制表示法打印 6 字节 MAC/FDDI 地址。M
和 m
说明符分别打印带(M)或不带(m)字节分隔符的地址。默认的字节分隔符是冒号(:)。
对于 FDDI 地址,F
说明符可以在 M
说明符之后使用,以使用破折号(-)分隔符而不是默认分隔符。
对于蓝牙地址,R
说明符应在 M
说明符之后使用,以使用反转字节顺序,适用于视觉解释采用小端序的蓝牙地址。
通过引用传递。
IPv4 地址¶
%pI4 1.2.3.4
%pi4 001.002.003.004
%p[Ii]4[hnbl]
用于打印 IPv4 点分隔十进制地址。I4
和 i4
说明符分别打印带(i4)或不带(I4)前导零的地址。
附加的 h
、n
、b
和 l
说明符分别用于指定主机、网络、大端或小端顺序的地址。如果没有提供说明符,则使用默认的网络/大端顺序。
通过引用传递。
IPv6 地址¶
%pI6 0001:0002:0003:0004:0005:0006:0007:0008
%pi6 00010002000300040005000600070008
%pI6c 1:2:3:4:5:6:7:8
用于打印 IPv6 网络序 16 位十六进制地址。I6
和 i6
说明符分别打印带(I6)或不带(i6)冒号分隔符的地址。始终使用前导零。
附加的 c
说明符可以与 I
说明符一起使用,以打印 https://tools.ietf.org/html/rfc5952 中描述的压缩 IPv6 地址。
通过引用传递。
IPv4/IPv6 地址(通用,带端口、流信息、范围)¶
%pIS 1.2.3.4 or 0001:0002:0003:0004:0005:0006:0007:0008
%piS 001.002.003.004 or 00010002000300040005000600070008
%pISc 1.2.3.4 or 1:2:3:4:5:6:7:8
%pISpc 1.2.3.4:12345 or [1:2:3:4:5:6:7:8]:12345
%p[Ii]S[pfschnbl]
用于打印 IP 地址,无需区分它是 AF_INET 类型还是 AF_INET6 类型。一个指向有效 struct sockaddr 的指针,通过 IS
或 iS
指定,可以传递给此格式说明符。
附加的 p
、f
和 s
说明符分别用于指定端口 (IPv4, IPv6)、流信息 (IPv6) 和范围 (IPv6)。端口带 :
前缀,流信息带 /
,范围带 %
,每个后面跟着实际值。
如果是 IPv6 地址,如果给定附加说明符 c
,则使用 https://tools.ietf.org/html/rfc5952 中描述的压缩 IPv6 地址。如果存在附加说明符 p
、f
或 s
,则 IPv6 地址将像 https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 建议的那样,用 [
、]
括起来。
对于 IPv4 地址,附加的 h
、n
、b
和 l
说明符也可以使用,并且在 IPv6 地址的情况下会被忽略。
通过引用传递。
更多示例
%pISfc 1.2.3.4 or [1:2:3:4:5:6:7:8]/123456789
%pISsc 1.2.3.4 or [1:2:3:4:5:6:7:8]%1234567890
%pISpfc 1.2.3.4:12345 or [1:2:3:4:5:6:7:8]:12345/123456789
UUID/GUID 地址¶
%pUb 00010203-0405-0607-0809-0a0b0c0d0e0f
%pUB 00010203-0405-0607-0809-0A0B0C0D0E0F
%pUl 03020100-0504-0706-0809-0a0b0c0e0e0f
%pUL 03020100-0504-0706-0809-0A0B0C0E0E0F
用于打印 16 字节 UUID/GUID 地址。附加的 l
、L
、b
和 B
说明符用于指定小端顺序的小写(l)或大写(L)十六进制表示法,以及大端顺序的小写(b)或大写(B)十六进制表示法。
如果没有使用附加说明符,则将打印默认的大端顺序,使用小写十六进制表示法。
通过引用传递。
dentry 名称¶
%pd{,2,3,4}
%pD{,2,3,4}
用于打印 dentry 名称;如果与 d_move()
发生竞争,名称可能是新旧名称的混合,但不会出错。%pd dentry 是我们以前使用的 %s dentry->d_name.name 的更安全的等效项,%pd<n> 打印最后 n
个组件。%pD 对 struct file
执行相同的操作。
通过引用传递。
block_device 名称¶
%pg sda, sda1 or loop0p1
用于打印 block_device 指针的名称。
struct va_format¶
%pV
用于打印 struct va_format 结构体。这些结构体包含一个格式字符串和 va_list,如下所示:
struct va_format {
const char *fmt;
va_list *va;
};
实现“递归 vsnprintf”。
在没有某种机制来验证格式字符串和 va_list 参数的正确性之前,请勿使用此功能。
通过引用传递。
设备树节点¶
%pOF[fnpPcCF]
用于打印设备树节点结构。默认行为等同于 %pOFf。
f - 设备节点 full_name
n - 设备节点名称
p - 设备节点 phandle
P - 设备节点路径规范(name + @unit)
F - 设备节点标志
c - 主要兼容字符串
C - 完整兼容字符串
使用多个参数时的分隔符是 ‘:’
示例
%pOF /foo/bar@0 - Node full name
%pOFf /foo/bar@0 - Same as above
%pOFfp /foo/bar@0:10 - Node full name + phandle
%pOFfcF /foo/bar@0:foo,device:--P- - Node full name +
major compatible string +
node flags
D - dynamic
d - detached
P - Populated
B - Populated bus
通过引用传递。
Fwnode 句柄¶
%pfw[fP]
用于打印 fwnode 句柄信息。默认是打印完整的节点名称,包括路径。修饰符在功能上等同于上面的 %pOF。
f - 节点的完整名称,包括路径
P - 节点的名称,包括地址(如果存在)
示例(ACPI)
%pfwf \_SB.PCI0.CIO2.port@1.endpoint@0 - Full node name
%pfwP endpoint@0 - Node name
示例(OF)
%pfwf /ocp@68000000/i2c@48072000/camera@10/port/endpoint - Full name
%pfwP endpoint - Node name
时间和日期¶
%pt[RT] YYYY-mm-ddTHH:MM:SS
%pt[RT]s YYYY-mm-dd HH:MM:SS
%pt[RT]d YYYY-mm-dd
%pt[RT]t HH:MM:SS
%pt[RT][dt][r][s]
用于以人类可读格式打印日期和时间,表示为
R struct rtc_time structure
T time64_t type
以人类可读格式。
默认情况下,年份将增加 1900,月份将增加 1。使用 %pt[RT]r(原始)可禁止此行为。
%pt[RT]s(空格)将通过使用 ' '(空格)而不是 'T'(大写 T)作为日期和时间之间的分隔符来覆盖 ISO 8601 分隔符。当日期或时间省略时,它不会产生任何效果。
通过引用传递。
struct clk¶
%pC pll1
用于打印 struct clk 结构体。%pC 打印时钟的名称(通用时钟框架)或唯一的 32 位 ID(传统时钟框架)。
通过引用传递。
位图及其派生类型,例如 cpumask 和 nodemask¶
%*pb 0779
%*pbl 0,3-6,8-10
用于打印位图及其派生类型,例如 cpumask 和 nodemask,%*pb 输出位图,字段宽度为位数,%*pbl 输出位图为范围列表,字段宽度为位数。
字段宽度按值传递,位图按引用传递。提供了辅助宏 cpumask_pr_args() 和 nodemask_pr_args() 以方便打印 cpumask 和 nodemask。
标志位域,例如页标志和 gfp_flags¶
%pGp 0x17ffffc0002036(referenced|uptodate|lru|active|private|node=0|zone=2|lastcpupid=0x1fffff)
%pGg GFP_USER|GFP_DMA32|GFP_NOWARN
%pGv read|exec|mayread|maywrite|mayexec|denywrite
用于将标志位域打印为一组将构成该值的符号常量。标志的类型由第三个字符给出。目前支持的有
p - [p]age flags,期望类型为 (
unsigned long *
) 的值v - [v]ma_flags,期望类型为 (
unsigned long *
) 的值g - [g]fp_flags,期望类型为 (
gfp_t *
) 的值
标志名称和打印顺序取决于特定类型。
请注意,此格式不应直接在跟踪点的 TP_printk()
部分中使用。相反,请使用 <trace/events/mmflags.h> 中的 show_*_flags() 函数。
通过引用传递。
网络设备特性¶
%pNF 0x000000000000c000
用于打印 netdev_features_t。
通过引用传递。
V4L2 和 DRM FourCC 代码(像素格式)¶
%p4cc
打印 V4L2 或 DRM 使用的 FourCC 代码,包括格式的字节序及其数值的十六进制表示。
通过引用传递。
示例
%p4cc BG12 little-endian (0x32314742)
%p4cc Y10 little-endian (0x20303159)
%p4cc NV12 big-endian (0xb231564e)
通用 FourCC 代码¶
- ::
%p4c[h[R]lb] gP00 (0x67503030)
打印一个通用 FourCC 代码,既以 ASCII 字符形式,也以十六进制数值形式。
通用 FourCC 代码总是以大端格式打印,即最高有效字节在前。这与 V4L/DRM FourCCs 相反。
附加的 h
、hR
、l
和 b
说明符定义用于加载存储字节的字节序。数据可以被解释为主机字节序、反转主机字节序、小端字节序或大端字节序。
通过引用传递。
小端机器的示例,给定 &(u32)0x67503030
%p4ch gP00 (0x67503030)
%p4chR 00Pg (0x30305067)
%p4cl gP00 (0x67503030)
%p4cb 00Pg (0x30305067)
大端机器的示例,给定 &(u32)0x67503030
%p4ch gP00 (0x67503030)
%p4chR 00Pg (0x30305067)
%p4cl 00Pg (0x30305067)
%p4cb gP00 (0x67503030)
Rust¶
%pA
仅供 Rust 代码用于格式化 core::fmt::Arguments
。请勿在 C 代码中使用。
鸣谢¶
如果您添加其他 %p 扩展,请尽可能在 <lib/tests/printf_kunit.c> 中添加一个或多个测试用例。
感谢您的合作与关注。