符號化器標記格式

概述

本文檔定義了一種日誌訊息的文字格式,該格式可以由符號化過濾器處理。基本概念是,日誌代碼發出包含原始位址值等的文字,而日誌代碼不進行任何實際工作來將這些值轉換為人類可讀的形式。相反,日誌文字使用此處定義的標記格式來識別應在事後轉換為人類可讀形式的資訊片段。與其他標記格式一樣,期望是大多數文字將按原樣顯示,而標記元素將被替換為擴展文字,或轉換為活動 UI 元素,以符號形式呈現更多詳細資訊。

這表示在運行時無需直接訪問符號表、DWARF 偵錯區段或類似資訊。運行時也無需任何旨在計算資訊的人類可讀呈現的邏輯,例如 C++ 符號反解。相反,日誌必須包含標記元素,這些元素提供必要的上下文資訊,以理解原始數據,例如記憶體佈局詳細資訊。

此格式使用既簡單又獨特的語法來識別標記元素。它足夠簡單,可以用直接的代碼進行匹配和解析。它足夠獨特,以至於看起來像標記元素開始或結束的字元序列,在日誌文字中很少或幾乎不會意外出現。它專門旨在不要求清理純文字,例如 HTML/XML 要求將 < 替換為 &lt; 等。

llvm-symbolizer 包含一個透過其 --filter-markup 選項的符號化過濾器。此外,當設定 LLVM_ENABLE_SYMBOLIZER_MARKUP 環境變數時,LLVM 公用程式會以標記形式發出堆疊追蹤。

範圍與假設

符號化過濾器實作將獨立於日誌產生的目標作業系統和機器架構,以及過濾器運行的主機作業系統和機器架構。

此格式假設符號化過濾器處理完整的整行。如果長行可能在日誌管線的某些階段被分割,則必須重新組裝它們以恢復原始換行符,然後再將行饋送到符號化過濾器中。大多數標記元素必須完全出現在單行上(通常在標記元素之前和/或之後帶有其他文字)。有些標記元素被指定為跨越多行,元素中間有換行符。即使在這些情況下,也不期望過濾器處理標記元素內任意位置的換行符,而僅處理某些欄位內的換行符。

此格式假設符號化過濾器處理來自單個進程位址空間上下文的連貫日誌行流。如果日誌流交錯來自多個進程的日誌行,則必須將這些日誌行整理成單獨的每個進程日誌流,並且每個流由符號化過濾器的單獨實例處理。由於核心和使用者進程在大多數作業系統中使用不相交的位址區域,因此如果需要,單個使用者進程位址空間加上核心位址空間可以被視為用於符號化目的的單個位址空間。

對建置 ID 的依賴性

符號化器標記方案依賴於運行時記憶體位址佈局的上下文資訊,以使其能夠將標記元素轉換為有用的符號形式。這依賴於對每個位址載入哪個二進制檔案具有明確的識別。

ELF 建置 ID 是名稱為 "GNU" 和類型為 NT_GNU_BUILD_ID 的 ELF 註解的有效負載,這是一個唯一位元組序列,用於識別特定的二進制檔案(可執行檔、共享程式庫、可載入模組或驅動程式模組)。鏈接器根據雜湊自動產生此 ID,該雜湊包含完整的符號表和偵錯資訊,即使稍後從二進制檔案中剝離也是如此。

此規範使用 ELF 建置 ID 作為識別二進制檔案的唯一手段。與日誌相關的每個二進制檔案都必須已使用唯一的建置 ID 鏈接。符號化過濾器必須具有將建置 ID 映射回原始 ELF 二進制檔案的某種方式(可以是整個未剝離的二進制檔案,也可以是與單獨的偵錯檔案配對的剝離二進制檔案)。

色彩化

標記格式支援 ANSI X3.64 SGR(選擇圖形再現)控制序列的受限子集。這些與其他標記元素不同

  • 它們指定呈現細節(粗體或顏色),而不是語義資訊。與顏色關聯語義含義(例如,紅色表示錯誤)是由執行日誌記錄的代碼選擇的,而不是由符號化過濾器的 UI 呈現選擇的。這是對現有代碼(例如 LLVM 清理器運行時)的讓步,這些代碼使用特定顏色,並且需要進行大量更改才能產生語義標記。

  • 單個控制序列更改「狀態」,而不是圍繞受影響文字的階層結構。

過濾器僅在單行內處理 ANSI SGR 控制序列。如果遇到進入粗體或顏色狀態的控制序列,則預期在該行結束之前會遇到重置為預設狀態的控制序列。如果在行尾留下「懸空」狀態,則過濾器可能會將下一行重置為預設狀態。

SGR 控制序列不會在任何其他標記元素內解釋。但是,其他標記元素可能會出現在 SGR 控制序列之間,並且顏色/粗體狀態預期適用於替換過濾器輸出中標記元素的符號輸出。

接受的 SGR 控制序列都具有 "\033[%um" 形式(此處使用 C 字串語法表示),其中 %u 是以下之一

代碼

效果

註解

0

重置為預設格式。

1

粗體文字

與顏色狀態結合,不會重置它們。

30

黑色前景

31

紅色前景

32

綠色前景

33

黃色前景

34

藍色前景

35

洋紅色前景

36

青色前景

37

白色前景

通用標記元素語法

所有標記元素都共享一個通用的語法結構,以方便簡單的匹配和解析代碼。每個元素的格式為

{{{tag:fields}}}

tag 識別以下描述的元素類型之一,並且始終是一個簡短的字母字串,必須使用小寫字母。元素的其餘部分由一個或多個欄位組成。欄位由 : 分隔,並且不能包含任何 :} 字元。對於每種元素類型,指定必須或可能存在的欄位數量以及它們包含的內容。

在欄位的內容中,不會解釋任何標記元素或 ANSI SGR 控制序列。

實作必須忽略預期欄位之後的標記欄位;這允許向後相容地擴展元素來新增欄位。實作不需要靜默地忽略它們,但元素的行為應像欄位被移除一樣。

在每種元素類型的描述中,printf 樣式的佔位符指示欄位內容

%s

可列印字元的字串,不包括 :}

%p

位址值,以 0x 開頭,後跟偶數個十六進制數字(使用小寫或大寫字母 AF)。如果數字全部為 0,則可以省略 0x 前綴。預期單個值中最多出現 16 個十六進制數字(64 位)。

%u

非負十進制整數。

%i

非負整數。如果以 0x 為前綴,則數字為十六進制;如果以 0 為前綴,則為八進制;否則為十進制。

%x

偶數個十六進制數字的序列(使用小寫或大寫字母 AF),沒有 0x 前綴。這表示任意位元組序列,例如 ELF 建置 ID。

呈現元素

這些元素傳達要以人類可讀的符號形式顯示的特定程式實體。

{{{symbol:%s}}}

此處 %s 是符號或類型的鏈接名稱。它可能需要根據語言 ABI 規則進行反解。即使對於未反解的名稱,也建議使用此標記元素來識別符號名稱,以便可以以獨特的方式呈現它。

範例

{{{symbol:_ZN7Mangled4NameEv}}}
{{{symbol:foobar}}}

{{{pc:%p}}}, {{{pc:%p:ra}}}, {{{pc:%p:pc}}}

此處 %p 是程式碼位置的記憶體位址。它可能會呈現為函數名稱和原始碼位置。後兩種形式區分了程式碼位置的種類,如下面的 bt 元素詳細描述的那樣。

範例

{{{pc:0x12345678}}}
{{{pc:0xffffffff9abcdef0}}}

{{{data:%p}}}

此處 %p 是資料位置的記憶體位址。它可能會呈現為該位置的全局變數的名稱。

範例

{{{data:0x12345678}}}
{{{data:0xffffffff9abcdef0}}}

{{{bt:%u:%p}}}, {{{bt:%u:%p:ra}}}, {{{bt:%u:%p:pc}}}

這表示回溯中的一個堆疊幀。它通常單獨出現在一行中(僅被空白字元包圍),在一系列這樣的行中,堆疊幀編號遞增。因此,人類可讀的輸出可能會被格式化為假設這種情況,使其對於每個 bt 元素單獨位於其行上的序列看起來良好,並且每行具有統一的縮排。但它可以出現在任何位置,因此過濾器不應移除元素周圍的任何非空白字元文字。

此處 %u 是堆疊幀編號,對於正在識別的故障位置,從零開始,對於堆疊幀零的調用堆疊幀的調用者遞增為一,對於堆疊幀一的調用者遞增為二,依此類推。%p 是程式碼位置的記憶體位址。

回溯中的程式碼位置來自兩個不同的來源。大多數回溯堆疊幀描述返回位址程式碼位置,即緊接在調用指令之後的指令。這是尚未運行的程式碼的位置,因為在那裡調用的函數尚未返回。因此,實際感興趣的程式碼位置通常是調用站點本身,而不是返回位址,即早一個指令。在呈現返回位址堆疊幀的原始碼位置時,符號化過濾器將從調用站點的實際返回位址中減去一個位元組或一個指令長度,目的是使記錄的位址可以直接轉換為調用站點的原始碼位置,而不是之後的顯式返回站點(這可能會造成混淆)。當涉及內聯函數時,調用站點和返回站點可能會出現在完全不相關的原始碼位置的不同函數中,而不僅僅是一行之隔,這使得顯示返回站點而不是調用站點的混淆非常嚴重。

通常,回溯中的第一個堆疊幀(「堆疊幀零」)識別故障、陷阱或異步中斷的精確程式碼位置,而不是返回位址。在其他時候,即使是第一個堆疊幀實際上也是返回位址(例如,在物件分配時收集的回溯,並在稍後使用或濫用分配的物件時報告)。當系統支援線程內陷阱處理時,在第一個堆疊幀之後也可能存在表示精確中斷的程式碼位置而不是返回位址的堆疊幀,這些堆疊幀呈現為陷阱處理函數的「調用者」(例如,POSIX 系統中的訊號處理程序)。

返回位址堆疊幀由 :ra 後綴識別。精確程式碼位置堆疊幀由 :pc 後綴識別。

傳統做法通常是將回溯收集為簡單的位址列表,從而丟失了返回位址程式碼位置和精確程式碼位置之間的區別。某些這樣的代碼將上述「減一」調整應用於位址值,然後再報告它們,並且並不總是清楚或一致是否已應用此調整。沒有 :ra:pc 後綴的 btpc 形式支援這些不明確的情況,這表明不清楚這是哪種類型的程式碼位置。但是,強烈建議所有發射器都使用帶後綴的形式,並交付未應用任何調整的位址值。當傳統做法不明確時,大多數情況似乎都是列印作為返回位址程式碼位置的位址,並且在未進行調整的情況下列印它們。因此,符號化過濾器通常會將「減一字節」調整應用於未列印歧義後綴的位址。假設在所有支援的機器上,調用指令都長於一個位元組,則第二次應用「減一字節」調整仍然會導致位址位於調用指令中的某個位置,因此這裡的少許草率通常幾乎沒有或沒有危害。

範例

{{{bt:0:0x12345678:pc}}}
{{{bt:1:0xffffffff9abcdef0:ra}}}

{{{hexdict:...}}} [1]

此元素可以跨越多行。此處 ... 是鍵值對的序列,其中單個 : 將每個鍵與其值分開,任意空白字元分隔這些對。每個對的值(右側)可以是 一個或多個 0 數字,也可以是 0x 後跟十六進制數字。每個值可能是記憶體位址,也可能是其他整數(包括看起來像可能的記憶體位址但實際上具有不相關用途的整數)。當關於記憶體佈局的上下文資訊表明給定值可能是程式碼位置或全局變數資料位址時,它可能會呈現為原始碼位置或變數名稱,或具有使這種解釋可選可見的活動 UI。

預期的用途是用於寄存器轉儲之類的東西,其中發射器不知道哪些值可能具有符號解釋,但使合理的符號解釋可用的呈現對於閱讀日誌的人來說可能非常有用。同時,純文字呈現通常應避免過多干擾轉儲的原始內容和格式。例如,它可能會使用帶有原始碼位置的腳註來表示看起來像是程式碼位置的值。活動 UI 呈現可能會按原樣顯示轉儲文字,但突出顯示具有可用符號資訊的值,並在選擇值時彈出符號詳細資訊的呈現。

範例

{{{hexdict:
    CS:                   0 RIP:     0x6ee17076fb80 EFL:            0x10246 CR2:                  0
    RAX:      0xc53d0acbcf0 RBX:     0x1e659ea7e0d0 RCX:                  0 RDX:     0x6ee1708300cc
    RSI:                  0 RDI:     0x6ee170830040 RBP:     0x3b13734898e0 RSP:     0x3b13734898d8
    R8:      0x3b1373489860 R9:          0x2776ff4f R10:     0x2749d3e9a940 R11:              0x246
    R12:     0x1e659ea7e0f0 R13: 0xd7231230fd6ff2e7 R14:     0x1e659ea7e108 R15:      0xc53d0acbcf0
  }}}

觸發元素

這些元素會導致外部操作,並將以人類可讀的形式呈現給使用者。通常,它們會觸發外部操作發生,從而產生可鏈接的頁面。然後,可以向使用者呈現鏈接或有關外部操作的其他資訊。

{{{dumpfile:%s:%s}}} [1]

此處第一個 %s 是轉儲類型的標識符,第二個 %s 是剛剛發布的特定轉儲的標識符。「已發布」的轉儲類型、確切含義以及標識符的性質超出標記格式本身的範圍。一般來說,它可能對應於按名稱寫入檔案或類似的操作。

此元素可能會觸發超出符號化標記的額外後處理工作。它表示已發布某種類型的轉儲檔案。附加到符號化過濾器的一些邏輯可以理解某些類型的轉儲檔案,並在遇到此元素時觸發對轉儲檔案的額外後處理(例如,產生視覺化效果、符號化)。預期從日誌記錄流中的上下文元素(如下所述)收集的資訊可能對於解碼轉儲的內容是必要的。因此,如果符號化過濾器觸發其他處理,則它可能需要將上下文資訊的某些精簡形式饋送到這些進程。

類型標識符的範例是 sancov,用於來自 LLVM SanitizerCoverage 的轉儲。

範例

{{{dumpfile:sancov:sancov.8675}}}

上下文元素

這些元素提供將呈現元素轉換為符號形式所需的資訊。與呈現元素不同,它們與周圍文字沒有直接關係。上下文元素應單獨出現在沒有其他非空白字元文字的行上,以便符號化過濾器可能會從其輸出中刪除整行,而不會隱藏任何其他日誌文字。

上下文元素本身不一定需要在人類可讀的輸出中呈現。但是,即使在符號化之後,它們傳達的資訊也可能對於理解日誌記錄文字至關重要。因此,建議在原始帶有標記的原始日誌可能不再容易訪問時,以某種形式保留此資訊。

上下文元素應在需要之前出現在日誌記錄流中。也就是說,如果某些上下文片段可能會影響符號化過濾器如何解釋或呈現稍後的呈現元素,則必要的上下文元素應已出現在日誌記錄流中的較早位置。符號化過濾器應始終可以實作為對原始日誌記錄流的單次遍歷,在遍歷時累積上下文並整理文字。

{{{reset}}}

這應在任何其他上下文元素之前輸出。需要此上下文元素是為了支援處理來自多個進程的日誌的實作。此類實作可能不知道新進程何時開始或結束。由於某些識別資訊(如進程 ID)在舊進程和新進程之間可能相同,因此需要一種方法來區分具有此類相同識別資訊的兩個進程。此元素通知此類實作重置過濾器的狀態,以便不為恰好具有相同識別資訊的新進程假設來自先前進程的上下文元素的資訊。

{{{module:%i:%s:%s:...}}}

此元素表示所謂的「模組」。 「模組」是單個鏈接的二進制檔案,例如載入的 ELF 檔案。通常,每個模組佔用連續的記憶體範圍。

此處 %i 是模組 ID,其他上下文元素使用它來引用此模組。第一個 %s 是模組的人類可讀標識符,例如 ELF DT_SONAME 字串或檔案名稱;但它可能為空。它僅用於隨意資訊。只有模組 ID 用於在其他上下文元素中引用此模組,永遠不會使用 %s 字串。定義模組 ID 的 module 元素必須始終在引用該模組 ID 的任何其他元素之前發出,以便過濾器永遠不需要追蹤懸空的引用。第二個 %s 是模組類型,它決定了剩餘欄位是什麼。支援以下模組類型

  • elf:%x

此處 %x 編碼 ELF 建置 ID。建置 ID 應引用單個鏈接的二進制檔案。建置 ID 字串是從中載入此模組的二進制檔案的唯一識別方式。

範例

{{{module:1:libc.so:elf:83238ab56ba10497}}}

{{{mmap:%p:%i:...}}}

此上下文元素用於提供有關記憶體中特定區域的資訊。%p 是起始位址,%i 以十六進制給出記憶體區域的大小。... 部分可以採用不同的形式,以提供有關指定記憶體區域的不同資訊。允許的形式如下

  • load:%i:%s:%p

此子元素通知過濾器,從模組載入了區段。模組由其模組 ID %i 識別。%s 是一個或多個字母 ‘r’、‘w’ 和 ‘x’(按該順序,並且使用大寫或小寫),以指示此記憶體區段是可讀、可寫和/或可執行的。符號化過濾器可以使用此資訊來猜測位址是給定模組中可能的程式碼位址還是可能的資料位址。剩餘的 %p 給出模組相對位址。對於 ELF 檔案,模組相對位址將是關聯的程式標頭的 p_vaddr。例如,如果您的模組的可執行區段具有 p_vaddr=0x1000p_memsz=0x1234,並且載入在 0x7acba69d5000,那麼您需要從 0x7acba69d50000x7acba69d6234 之間的任何位址中減去 0x7acba69d4000,以獲得模組相對位址。起始位址通常已向下捨入到活動頁面大小,並且大小已向上捨入。

範例

{{{mmap:0x7acba69d5000:0x5a000:load:1:rx:0x1000}}}

腳註