詳述 Golang 的符號表
???本文基于 Go 1.13。
符號表是由編譯器生成和維護的,保存了與程序相關的信息,如函數和全局變量。理解符號表能幫助我們更好地與之交互和利用它。
符號表
Go 編譯的所有二進制文件默認內嵌了符號表。我們來舉一個例子并研究它。下面是代碼:
var?AppVersion?string
func?main()?{
?fmt.Println(`Version:?`+AppVersion)
}
可以通過命令?nm
?來展示符號表;下面是從?OSX?的結果中提取的部分信息:
0000000001177220?b?io.ErrUnexpectedEOF
[...]
0000000001177250?b?main.AppVersion
00000000010994c0?t?main.main
[...]
0000000001170b00?d?runtime.buildVersion
用?b
(全稱為?bss)標記的符號是未初始化的數據。由于我們前面的變量?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的文章“不尋常的加速:二進制文件大小”。
鏈接:https://medium.com/a-journey-with-go/go-how-to-take-advantage-of-the-symbols-table-360dd52269e5
(版權歸原作者所有,侵刪)