在 Golang 中使用 -w 和 -s 標誌

語言: CN / TW / HK

爭做團隊核心程序員,關注「 幽鬼

今天的博客文章來自 Valery,這是 Spiral Scout [1] 的一名高級軟件工程師,專門從事 Golang(Go)開發。

作為在 Golang 以及許多其他編程語言方面具有專業知識的軟件開發機構,我們知道對於我們的工程師和質量保證專家而言,能夠與社區分享他們的知識和經驗非常重要。感謝 Valery 這篇出色的文章和有用的 Golang 測試技巧!

當我在 GitHub 上查找一些良好的工程實踐以備應用時,我注意到許多開發人員編譯他們的 Go 程序時經常出現的問題,他們中許多人都使用鏈接器標記(linker flags)來減小輸出文件大小,尤其是同時使用 -w-s 標記所帶來的疊加效果。

在軟件測試中,標記也被稱為參數。當從命令行運行程序時,它們用於標識特定的狀態或條件。標記可以打開或關閉,並且在整個軟件開發過程中大量語言和框架都採用這種方式。

本文致力於説明在 Go 中實現 -w-s 標誌的效果,並提供可以更有效地使用它們的方法。

本文是 Go語言中文網組織的 GCTT 翻譯,發佈在 Go語言中文網公眾號,轉載請聯繫我們授權。

-w-s 標誌如何與 DWARF 和 ELF 配合使用

在討論何時以及如何使用 -w-s 標誌之前,先簡要介紹一下我的測試環境。我使用的硬件/軟件組合包括:

  • A Dell XPS 9570 laptop

  • Manjaro Linux OS

  • Testing branch

-w-s 標誌通常用在 App 鏈接階段和 Go 編譯階段 -ldflags 指令結合使用 (參見 Go 命令文檔 [2] )。有關標誌的更多信息,請參見:https://golang.org/cmd/link/。

在我們仔細查看 -w 標誌並拆解二進制代碼以檢查 DWARF 符號表是否消失之前,我建議先明確 DWARF 符號表的定義。

DWARF 是一種可以包含在二進制文件中的調試數據格式。根據維基百科 DWARF 條目 [3] ,此格式是與稱為 ELF(可執行和可鏈接格式)的標準通用文件格式一起開發的。 這篇文章 [4] 很好地解釋了調試器如何與 DWARF 表配合工作。

Golang 的創建者們在 Go DWARF 源代碼 [5] 中分享了更多信息,包括有關如何形成此表並將其嵌入以 Go 編寫的二進制文件的詳細信息。

我將通過示例代碼介紹以下一些要點。

首先,我們要使用以下步驟讀取 DWARF 信息:

  1. 編譯 Go 程序(開始我們僅使用 Go build 命令)

go build -o simple_build cmd/main.go
  1. 讀取符號表。使用 readelf -Ws 可以方便的實現符號表讀取。但是你也可以使用其他更熟悉的工具讀取文件頭(比如 objdump -h )。

  2. 請注意生成的程序的頭部內容。

我們可以看到這個二進制文件中包含了用於調試的數據(從第 24 節到第 32 節),並且還有一個符號表和字符串表。(如下所述)

  1. 使用如下命令進行讀表

> objdump - dwarf=info main

輸出看起來有些長,所以我用下面的命令把輸出保存到文件中。

> objdump - dwarf=info main &> main.txt

你可以在下面看到輸出的一部分:

為了根據地址查找必要的函數,我們需要知道 PC (程序計數器)。你可以在 EIP 寄存器中找到 PC 值,它由 DW_AT_low_pc 和 DW_AT_high_pc 表示。舉個例子,對於 main.main 函數( main 是 Go 運行時的函數)使用 low_pc ,並嘗試使用 objdump -d 在二進制文件的位置 0x44f930 找到它。

很棒。現在我們使用 -w 標誌編譯程序並且和不使用標誌編譯出來的程序進行比較。

  1. 運行下面的命令

go build -ldflags=”-w” -o build_with_w cmd/main.go

然後看看生成文件的頭部發生了什麼變化:

正如我們看到的, .zdebug 部分完全消失了。通過對頂部低位(Off 列)地址相減,我們可以精確計算二進制文件減小了多少。當你把這個差值從 Bytes 轉換到 KB 時,可以對實際情況有更直觀的體會。

在這個案例中,二進制文件的總大小大約 25MB,這意味着我們節省了大約 3.7KB。這讓我好奇如果我嘗試使用 Delve Go 調試器工具 [6] 運行 dvl 時會發生什麼?

  1. 運行

dlv — listen=:43671 — headless=true — api-version=2 — accept-multiclient exec ./build_with_w

... 返回了你所期待的結果:

API server listening at: [::]:43671
could not launch process: could not open debug info

好了,現在關於 DWARF 表和 -w 標誌的作用變得更加清楚了。

讓我們繼續看看 -s 標誌。根據文檔, -s 標誌不僅刪除了調試信息,同時還刪除了指定的符號表。不過,它與 -s 標誌有何不同呢?

首先,快速瞭解一下 — 符號表包含了局部變量、全局變量和函數名等的信息。在上圖中,這些信息在第 26 和第 27 節(.symtab 和 .strtab)給出。更多關於符號表的詳細信息,可以在這裏找到: http://refspecs.linuxbase.org/elf/gabi4+/ch4.symtab.html [7]http://refspecs.linuxbase.org/elf/gabi4+/ch4.strtab.html [8] .

這次我們試試用 -s 標誌編譯一個二進制文件:

如同我們期待的一樣,關於 DWARF 的信息以及符號表和字符串表(一種發佈標誌)的內容都消失不見了。

這意味着什麼?

如果只想刪除調試信息,只使用 -w 標誌是最合適的。如果要另外刪除符號和字符串表以減小二進制文件的大小,請使用 -s 標誌。

下面是在 Golang 中使用這些 flag 的的 反面教材 ,不建議大家這樣使用。儘管兩個標誌似乎比一個標誌好,但是對於 -w-s 標誌,情況卻並非如此:

via: https://blog.spiralscout.com/using-w-and-s-flags-in-golang-97ae59b50e26

作者: John Griffin [9] 譯者: befovy [10] 校對: polaris1119 [11]

本文由 GCTT [12] 原創編譯, Go 中文網 [13] 榮譽推出,發佈在 Go語言中文網公眾號,轉載請聯繫我們授權。

參考資料

[1]

Spiral Scout: https://spiralscout.com/

[2]

Go 命令文檔: https://golang.org/src/cmd/go/alldocs.go

[3]

DWARF 條目: https://en.wikipedia.org/wiki/DWARF

[4]

這篇文章: https://eli.thegreenplace.net/2011/02/07/how-debuggers-work-part-3-debugging-information/

[5]

Go DWARF 源代碼: https://golang.org/src/cmd/link/internal/ld/dwarf.go

[6]

Delve Go 調試器工具: https://github.com/go-delve/delve

[7]

http://refspecs.linuxbase.org/elf/gabi4+/ch4.symtab.html: http://refspecs.linuxbase.org/elf/gabi4+/ch4.symtab.html

[8]

http://refspecs.linuxbase.org/elf/gabi4+/ch4.strtab.html: http://refspecs.linuxbase.org/elf/gabi4+/ch4.strtab.html

[9]

John Griffin: https://blog.spiralscout.com/@johnwgriffin

[10]

befovy: https://github.com/befovy

[11]

polaris1119: https://github.com/polaris1119

[12]

GCTT: https://github.com/studygolang/GCTT

[13]

Go 中文網: https://studygolang.com/

歡迎關注「 幽鬼 」,像她一樣做團隊的核心。