英语

如何正确使用 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

如果 <type> 的大小依赖于体系结构(例如,cycles_t、tcflag_t)或依赖于配置选项(例如,blk_status_t),请使用其最大可能类型的格式说明符并显式将其转换为该类型。

示例

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() 为 true 的指针)作为符号错误名称。对于没有已知符号名称的错误值,以十进制形式打印,而作为 %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

Ss 说明符用于以符号格式打印指针。它们产生带有 (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

ku 说明符用于打印先前从内核内存 (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]

用于打印结构资源。Rr 说明符产生带有 (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]

用于打印结构范围。结构范围保存任意的 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 地址。Mm 说明符将生成带有 (M) 或不带 (m) 字节分隔符的打印地址。默认字节分隔符是冒号 (:)。

对于 FDDI 地址,可以在 M 说明符之后使用 F 说明符,以使用短划线 (-) 分隔符而不是默认分隔符。

对于蓝牙地址,应在 M 说明符之后使用 R 说明符,以使用适合蓝牙地址视觉解释的字节反转顺序,该顺序为小端字节序。

按引用传递。

IPv4 地址

%pI4    1.2.3.4
%pi4    001.002.003.004
%p[Ii]4[hnbl]

用于打印 IPv4 点分隔的十进制地址。I4i4 说明符将生成带有 (i4) 或不带 (I4) 前导零的打印地址。

附加的 hnbl 说明符用于分别指定主机、网络、大端或小端字节序地址。如果未提供说明符,则使用默认的网络/大端字节序。

按引用传递。

IPv6 地址

%pI6    0001:0002:0003:0004:0005:0006:0007:0008
%pi6    00010002000300040005000600070008
%pI6c   1:2:3:4:5:6:7:8

用于打印 IPv6 网络顺序的 16 位十六进制地址。I6i6 说明符将生成带有 (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 类型。可以通过 ISiS 指定传递指向有效 struct sockaddr 的指针给此格式说明符。

附加的 pfs 说明符用于指定端口 (IPv4, IPv6)、流信息 (IPv6) 和范围 (IPv6)。端口具有 : 前缀,流信息具有 / 前缀,范围具有 % 前缀,每个前缀后跟实际值。

在 IPv6 地址的情况下,如果给出附加说明符 c,则将使用 https://tools.ietf.org/html/rfc5952 中描述的压缩 IPv6 地址。 在附加说明符 pfs 的情况下,IPv6 地址将被 [] 包围,如 https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 中建议的那样。

在 IPv4 地址的情况下,也可以使用附加的 hnbl 说明符,但在 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 地址。附加的 lLbB 说明符用于指定小端字节序,分别用小写 (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 个组件。 %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 - 设备节点路径规范(名称 + @单元)

  • 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   \[email protected]@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 (raw) 可抑制此行为。

%pt[RT]s (space) 将覆盖 ISO 8601 分隔符,在日期和时间之间使用“ ”(空格)而不是“T”(大写 T)。当省略日期或时间时,它将不起作用。

按引用传递。

struct clk

%pC     pll1
%pCn    pll1

用于打印 struct clk 结构。 %pC 和 %pCn 打印时钟的名称(通用时钟框架)或唯一的 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 标志,期望类型为 (unsigned long *) 的值

  • v - [v]ma_flags,期望类型为 (unsigned long *) 的值

  • g - [g]fp_flags,期望类型为 (gfp_t *) 的值

标志名称和打印顺序取决于特定类型。

请注意,不应在跟踪点的 TP_printk() 部分直接使用此格式。 请改用 中的 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)

Rust

%pA

仅供 Rust 代码用于格式化 core::fmt::Arguments。 请勿从 C 中使用它。

致谢

如果添加其他 %p 扩展,请尽可能在 <lib/test_printf.c> 中添加一个或多个测试用例。

感谢您的合作与关注。