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
投建器賦予了我們重寫數據符號(類型 b
或 d
)的能力,現在它們有了 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