詳述 Golang 的符號(hào)表
???本文基于 Go 1.13。
符號(hào)表是由編譯器生成和維護(hù)的,保存了與程序相關(guān)的信息,如函數(shù)和全局變量。理解符號(hào)表能幫助我們更好地與之交互和利用它。
符號(hào)表
Go 編譯的所有二進(jìn)制文件默認(rèn)內(nèi)嵌了符號(hào)表。我們來(lái)舉一個(gè)例子并研究它。下面是代碼:
var?AppVersion?string
func?main()?{
?fmt.Println(`Version:?`+AppVersion)
}
可以通過(guò)命令?nm
?來(lái)展示符號(hào)表;下面是從?OSX?的結(jié)果中提取的部分信息:
0000000001177220?b?io.ErrUnexpectedEOF
[...]
0000000001177250?b?main.AppVersion
00000000010994c0?t?main.main
[...]
0000000001170b00?d?runtime.buildVersion
用?b
(全稱(chēng)為?bss)標(biāo)記的符號(hào)是未初始化的數(shù)據(jù)。由于我們前面的變量?AppVersion
?沒(méi)有初始化,因此它屬于?b
。符號(hào)?d
?表示已初始化的數(shù)據(jù),t
?表示文本符號(hào), 函數(shù)屬于其中之一。
Go 也封裝了?nm
?命令,可以用命令?go tool nm
?來(lái)使用它,也能生成相同的結(jié)果:
1177220?B?io.ErrUnexpectedEOF
[...]
1177250?B?main.AppVersion
10994c0?T?main.main
[...]
1170b00?D?runtime.buildVersion
當(dāng)我們知道了暴露的變量的名字后,我們就可以與之交互。
自定義變量
當(dāng)執(zhí)行命令?go build
?時(shí),經(jīng)過(guò)了兩個(gè)階段:編譯和構(gòu)建。構(gòu)建階段通過(guò)編譯過(guò)程中生成的對(duì)象文件生成了一個(gè)可執(zhí)行文件。為了實(shí)現(xiàn)這個(gè)階段,構(gòu)建器把符號(hào)表中的符號(hào)重定向到最終的二進(jìn)制文件。
在 Go 中我們可以用?-X
?來(lái)重寫(xiě)一個(gè)符號(hào)定義,-X
?兩個(gè)入?yún)ⅲ好Q(chēng)和值。下面是承接前面的代碼的例子:
go?build?-o?ex?-ldflags="-X?main.AppVersion=v1.0.0"
構(gòu)建并運(yùn)行程序,現(xiàn)在會(huì)展示在命令行中定義的版本:
Version:?v1.0.0
運(yùn)行?nm
?命令會(huì)看到變量已被初始化:
1170a90?D?main.AppVersion
投建器賦予了我們重寫(xiě)數(shù)據(jù)符號(hào)(類(lèi)型?b
?或?d
)的能力,現(xiàn)在它們有了 Go 中的?string
?類(lèi)型。下面是那些符號(hào)列表:
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
,它們都是被構(gòu)建器自動(dòng)設(shè)置的。我們來(lái)看一下運(yùn)行時(shí)這些符號(hào)的意義。
調(diào)試
符號(hào)表的存在是為了確保標(biāo)識(shí)符在使用之前已被聲明。這意味著當(dāng)程序被構(gòu)建后,它就不再需要這個(gè)表了。然而,默認(rèn)情況下符號(hào)表是被嵌入到了 Go 的二進(jìn)制文件以便調(diào)試。我們先來(lái)理解如何利用它,之后再來(lái)看怎么把它從二進(jìn)制文件中刪除。
我會(huì)用?gdb
?來(lái)調(diào)試。只需要執(zhí)行?gdb ex
?就可以加載二進(jìn)制文件?,F(xiàn)在程序已被加載,我們用?list
?命令來(lái)展示源碼。下面是輸出:
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
?初始化的第一步是讀取符號(hào)表,為了提取程序中函數(shù)和符號(hào)的信息。我們現(xiàn)在可以用?-ldflags=-s
?參數(shù)不把符號(hào)表編譯進(jìn)程序。下面是新的輸出:
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.
現(xiàn)在調(diào)試器由于找不到符號(hào)表不能展示源碼。我們應(yīng)該留意到使用?-s
?參數(shù)去掉了符號(hào)表的同時(shí),也去掉了對(duì)調(diào)試器很有用的?[DWARF](https://golang.org/pkg/debug/dwarf/ "DWARF")
?調(diào)試信息。
二進(jìn)制文件的大小
去掉符號(hào)表后會(huì)讓調(diào)試器用起來(lái)很困難,但是會(huì)減少二進(jìn)制文件的大小。下面是有無(wú)符號(hào)表的二進(jìn)制文件的區(qū)別:
2,0M??7?f?é?v?15:59?ex
1,5M??7?f?é?v?15:22?ex-s
沒(méi)有符號(hào)表比有符號(hào)表會(huì)小 25%。下面是編譯?cmd/go
?源碼的另一個(gè)例子:
14M??7?f?é?v?16:58?go
11M??7?f?é?v?16:58?go-s
這里沒(méi)有符號(hào)表和 DWARF 信息,也小了 25%。
如果你想了解為什么二進(jìn)制文件會(huì)變小,我推薦你閱讀 WebKit 團(tuán)隊(duì)的?Benjamin Poulain的文章“不尋常的加速:二進(jìn)制文件大小”。
鏈接:https://medium.com/a-journey-with-go/go-how-to-take-advantage-of-the-symbols-table-360dd52269e5
(版權(quán)歸原作者所有,侵刪)