Go:符號表是什麼?如何使用?

本文基於 Go 1.13 ,原文及譯者信息在文末。

符號表是由編譯器生成和維護的,保存了與程序相關的信息,如函數和全局變量。理解符號表能幫助我們更好地與之交互和利用它。

符號表

Go 編譯的所有二進制文件默認內嵌了符號表。我們來舉一個例子並研究它。下面是代碼:

var AppVersion string

func main() {
 fmt.Println(`Version: `+AppVersion)
}

可以通過命令 nm 來展示符號表;下面是從 OSX[1] 的結果中提取的部分信息:

0000000001177220 b io.ErrUnexpectedEOF
[...]
0000000001177250 b main.AppVersion
00000000010994c0 t main.main
[...]
0000000001170b00 d runtime.buildVersion

b(全稱爲 bss[2])標記的符號是未初始化的數據。由於我們前面的變量 AppVersion 沒有初始化,因此它屬於 b。符號 d 表示已初始化的數據,t 表示文本符號, 函數屬於其中之一。

Go 也封裝了 nm 命令,可以用命令 go tool nm 來使用它,也能生成相同的結果:

1177220 B io.ErrUnexpectedEOF
[...]
1177250 B main.AppVersion
10994c0 T main.main
[...]
1170b00 D runtime.buildVersion

當我們知道了暴露的變量的名字後,我們就可以與之交互。

自定義變量

當執行命令 go build 時,經過了兩個階段:編譯和構建。構建階段通過編譯過程中生成的對象文件生成了一個可執行文件。爲了實現這個階段,構建器把符號表中的符號重定向到最終的二進制文件。

在 Go 中我們可以用 -X 來重寫一個符號定義,-X 兩個入參:名稱和值。下面是承接前面的代碼的例子:

go build -o ex -ldflags="-X main.AppVersion=v1.0.0"

構建並運行程序,現在會展示在命令行中定義的版本:

Version: v1.0.0

運行 nm 命令會看到變量已被初始化:

1170a90 D main.AppVersion

投建器賦予了我們重寫數據符號(類型 bd)的能力,現在它們有了 Go 中的 string 類型。下面是那些符號列表:

D runtime.badsystemstackMsg
D runtime.badmorestackgsignalMsg
D runtime.badmorestackg0Msg
B os.executablePath
B os.initCwd
B syscall.freebsdConfArch
D runtime/internal/sys.DefaultGoroot
B runtime.modinfo
B main.AppVersion
D runtime.buildVersion

在列表中我們看到了之前的變量和 DefaultGoroot,它們都是被構建器自動設置的。我們來看一下運行時這些符號的意義。

調試

符號表的存在是爲了確保標識符在使用之前已被聲明。這意味着當程序被構建後,它就不再需要這個表了。然而,默認情況下符號表是被嵌入到了 Go 的二進制文件以便調試。我們先來理解如何利用它,之後再來看怎麼把它從二進制文件中刪除。

我會用 gdb 來調試。只需要執行 gdb ex 就可以加載二進制文件。現在程序已被加載,我們用 list 命令來展示源碼。下面是輸出:

GNU gdb (GDB) 8.3.1
[...]
Reading symbols from ex...
Loading Go Runtime support.
(gdb) list 10
6
7  var AppVersion string
8
9  func main() {
10    fmt.Println(`Version: `+AppVersion)
11 }
12
(gdb)

gdb 初始化的第一步是讀取符號表,爲了提取程序中函數和符號的信息。我們現在可以用 -ldflags=-s 參數不把符號表編譯進程序。下面是新的輸出:

GNU gdb (GDB) 8.3.1
[...]
Reading symbols from ex...
(No debugging symbols found in ex)
(gdb) list
No symbol table is loaded.  Use the "file" command.

現在調試器由於找不到符號表不能展示源碼。我們應該留意到使用 -s 參數去掉了符號表的同時,也去掉了對調試器很有用的 [DWARF](https://golang.org/pkg/debug/dwarf/ "DWARF") 調試信息。

二進制文件的大小

去掉符號表後會讓調試器用起來很困難,但是會減少二進制文件的大小。下面是有無符號表的二進制文件的區別:

2,0M  7 f é v 15:59 ex
1,5M  7 f é v 15:22 ex-s

沒有符號表比有符號表會小 25%。下面是編譯 cmd/go 源碼的另一個例子:

14M  7 f é v 16:58 go
11M  7 f é v 16:58 go-s

這裏沒有符號表和 DWARF 信息,也小了 25%。

如果你想了解爲什麼二進制文件會變小,我推薦你閱讀 WebKit 團隊的 Benjamin Poulain[3] 的文章 “不尋常的加速:二進制文件大小 [4]”。


via: https://medium.com/a-journey-with-go/go-how-to-take-advantage-of-the-symbols-table-360dd52269e5

作者:Vincent Blanchon[5] 譯者:lxbwolf[6] 校對:polaris1119[7]

本文由 GCTT[8] 原創編譯,Go 中文網 [9] 榮譽推出

參考資料

[1]

OSX: https://www.unix.com/man-page/osx/1/nm/

[2]

bss: https://en.wikipedia.org/wiki/.bss

[3]

Benjamin Poulain: https://twitter.com/awfulben

[4]

不尋常的加速:二進制文件大小: https://webkit.org/blog/2826/unusual-speed-boost-size-matters/

[5]

Vincent Blanchon: https://medium.com/@blanchon.vincent

[6]

lxbwolf: https://github.com/lxbwolf

[7]

polaris1119: https://github.com/polaris1119

[8]

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

[9]

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/z20cFAQuGMKJGRzpcMsbvw