英語

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

如果 的大小依賴於架構(例如 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() 為真的指標)列印為符號錯誤名稱。對於沒有已知符號名稱的錯誤值,將以十進位制列印,而作為 %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]

用於列印 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 地址。Mm 說明符分別列印帶(M)或不帶(m)位元組分隔符的地址。預設的位元組分隔符是冒號(:)。

對於 FDDI 地址,F 說明符可以在 M 說明符之後使用,以使用破折號(-)分隔符而不是預設分隔符。

對於藍牙地址,R 說明符應在 M 說明符之後使用,以使用反轉位元組順序,適用於視覺解釋採用小端序的藍牙地址。

透過引用傳遞。

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 型別。一個指向有效 struct sockaddr 的指標,透過 ISiS 指定,可以傳遞給此格式說明符。

附加的 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> 列印最後 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 相反。

附加的 hhRlb 說明符定義用於載入儲存位元組的位元組序。資料可以被解釋為主機位元組序、反轉主機位元組序、小端位元組序或大端位元組序。

透過引用傳遞。

小端機器的示例,給定 &(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> 中新增一個或多個測試用例。

感謝您的合作與關注。