详述 Golang 的符号表

ℹ️本文基于 1.13。

符号表是由编译器生成和维护的,保存了与程序相关的信息,如函数和全局。理解符号表能帮助我们更好地与之交互和利用它。

符号表

Go 编译的所有二进制默认内嵌了符号表。我们来举一个例子并研究它。下面是

varAppVersion

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

可以通过命令nm来展示符号表;下面是从OSX的结果中提取的部分信息:

0000000001177220bio.ErrUnexpectedEOF
[...]
0000000001177250bmain.AppVersion
00000000010994c0tmain.main
[...]
0000000001170b00druntime.buildVersion

b(全称为bss)标记的符号是未初始化的。由于我们前面的变量AppVersion没有初始化,因此它属于b。符号d表示已初始化的数据,t表示文本符号, 函数属于其中之一。

Go 也封装了nm命令,可以用命令go tool nm来使用它,也能生成相同的结果:

1177220Bio.ErrUnexpectedEOF
[...]
1177250Bmain.AppVersion
10994c0Tmain.main
[...]
1170b00Druntime.buildVersion

当我们知道了暴露的变量的名字后,我们就可以与之交互。

自定义变量

当执行命令go 时,经过了两个阶段:编译和构建。构建阶段通过编译过程中生成的对象文件生成了一个可执行文件。为了实现这个阶段,构建器把符号表中的符号重定向到最终的二进制文件。

在 Go 中我们可以用-X来重写一个符号定义,-X两个入参:名称和值。下面是承接前面的代码的例子:

gobuild-oex-ldflags="-Xmain.AppVersion=v1.0.0"

构建并运行程序,现在会展示在命令行中定义的版本:

Version:v1.0.0

运行nm命令会看到变量已被初始化:

1170a90Dmain.AppVersion

投建器赋予了我们重写数据符号(类型bd)的能力,现在它们有了 Go 中的string类型。下面是那些符号列表:

Druntime.badsystemstackMsg
Druntime.badmorestackgsignalMsg
Druntime.badmorestackg0Msg
Bos.executablePath
Bos.initCwd
Bsyscall.freebsdConfArch
Druntime/internal/sys.DefaultGoroot
Bruntime.modinfo
Bmain.AppVersion
Druntime.buildVersion

在列表中我们看到了之前的变量和DefaultGoroot,它们都是被构建器自动设置的。我们来看一下运行时这些符号的意义。

调试

符号表的存在是为了确保标识符在使用之前已被声明。这意味着当程序被构建后,它就不再需要这个表了。然而,默认情况下符号表是被嵌入到了 Go 的二进制文件以便调试。我们先来理解如何利用它,之后再来看怎么把它从二进制文件中删除。

我会用gdb来调试。只需要执行gdb ex就可以加载二进制文件。现在程序已被加载,我们用list命令来展示。下面是输出:

GNUgdb(GDB)8.3.1
[...]
Readingsymbolsfromex...
LoadingGoRuntimesupport.
(gdb)list10
6
7varAppVersionstring
8
9funcmain(){
10fmt.Println(`Version:`+AppVersion)
11}
12
(gdb)

gdb初始化的第一步是读取符号表,为了提取程序中函数和符号的信息。我们现在可以用-ldflags=-s不把符号表编译进程序。下面是新的输出:

GNUgdb(GDB)8.3.1
[...]
Readingsymbolsfromex...
(Nodebuggingsymbolsfoundinex)
(gdb)list
Nosymboltableisloaded.Usethe""command.

现在调试器由于找不到符号表不能展示源码。我们应该留意到使用-s参数去掉了符号表的同时,也去掉了对调试器很有用的[DWARF](https://golang.org/pkg/debug/dwarf/ "DWARF")调试信息。

二进制文件的大小

去掉符号表后会让调试器用起来很困难,但是会减少二进制文件的大小。下面是有无符号表的二进制文件的区别:

2,0M7fév15:59ex
1,5M7fév15:22ex-s

没有符号表比有符号表会小 25%。下面是编译cmd/go源码的另一个例子:

14M7fév16:58go
11M7fév16:58go-s

这里没有符号表和 DWARF 信息,也小了 25%。

如果你想了解为什么二进制文件会变小,我推荐你阅读 WebKit 团队的Benjamin Poulain的文章“不寻常的加速:二进制文件大小”。

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

(版权归原作者所有,侵删)

给TA打赏
共{{data.count}}人
人已打赏
运维笔记

HW行动 |信息安全面试题目汇总(带答案)

2023-10-10 18:31:30

运维笔记

可落地的 8 种架构模式!

2023-10-10 18:31:32

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索