说来“从Golang的二进制程序中获取version”不过是一件很小的事情。大概是去年在学Go和使用eBPF捕获GoTLS加密数据时的一些小tips:
前·问题
在软件开发的历史长河中,有一个问题是所有人无法躲避的–兼容性。一种是你自己很NB不用,只需要自己兼容自己即可,一种是依赖别人,只要别人有所变动自己也要跟着改动。无论是哪种,能想象得到的,兼容适配的工作并不讨喜。
一个程序对于非开发人员而言,其语义化版本(Semantic Versioning)尤其重要,只有知道了一个01的文件对应的版本号,我们才能知道其变动了什么,特性是什么。
那,如何从Golang的二进制文件中获取其对应的版本号呢?
中·Golang
首先,我们知道有go version
的命令可以查看版本号,比如:
1
2
3
4
| ls -lh https_server
-rwxrwxr-x 1 xxx xxx 6.7M 12月 21 16:03 https_server
go version ./https_server
./https_server: go1.20.7
|
或者,我们粗暴点用strings
命令查看:
1
2
3
| strings https_server|grep -E "^go[0-9]+\.[0-9]+\.[0-9]+"
go1.20.7
go1.20.7
|
elf & readelf
但是,如果想要更深层次的了解这些信息是存放在哪里的,那么就需要了解一下Executable and Linkable Format即ELF文件类型,在Linux下主要有如下三种文件:可执行文件(.out)、可重定位文件(.o)和共享目标文件(.so),ELF文件结构大致如下。
![](/.read-infos-from-go-binary.assets/46fff5c0bd3cc9e38f273b3079b1249ab5334968.png)
图源 - https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
在 Linux 下 Go 编译出来的程序其实就是 ELF 文件类型的。因此,想要剖析 Go 的二进制文件还需要知道大概如下关于 ELF 的知识:
ELF header: 包含了magic、大小端、架构是32位还是64位……
Section Header Table: 存储了有关section信息,每个section对应section header table中的一个条目;
Program Header Table:存储了有关segment的信息,每个segment由一个或多个segment组成,内科在运行时用到这些信息;
section & segment区别 :
在可执行文件格式(如ELF文件)中,section是指文件的一个逻辑分段,它包含了特定类型的数据。
segment通常指的是程序被加载到内存时的一个物理或逻辑单元。在某些可执行文件格式中(如ELF),“segment"更具体地指由一个或多个section组成的,用于描述程序的内存映像的部分。
section主要用于编译和链接过程中的组织和管理;segment的划分是为了满足运行时的需求,比如内存保护、共享和权限管理。
.text : 存放程序的执行代码,只可读;
.data: 用于存放程序中已初始化的全局变量和静态变量。这些变量在程序启动之前由编译器赋予初始值;
.rodata: 即read-only-data,用于存放只读数据,比如字符串常量和其他任何程序运行时不需要修改的常量数据。
runtime.buildVersion
使用 readelf 可以看到Go二进制文件的section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| readelf -S https_server
There are 36 section headers, starting at offset 0x270:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000401000 00001000
00000000002372df 0000000000000000 AX 0 0 32
[ 2] .plt PROGBITS 00000000006382e0 002382e0
0000000000000210 0000000000000010 AX 0 0 16
[ 3] .rodata PROGBITS 0000000000639000 00239000
00000000000de3f3 0000000000000000 A 0 0 32
[ 4] .rela RELA 00000000007173f8 003173f8
0000000000000018 0000000000000018 A 11 0 8
[ 5] .rela.plt RELA 0000000000717410 00317410
0000000000000300 0000000000000018 A 11 2 8
[ 6] .gnu.version VERSYM 0000000000717720 00317720
000000000000004a 0000000000000002 A 11 0 2
[ 7] .gnu.version_r VERNEED 0000000000717780 00317780
0000000000000060 0000000000000000 A 10 1 8
[ 8] .hash HASH 00000000007177e0 003177e0
00000000000000b8 0000000000000004 A 11 0 8
[ 9] .shstrtab STRTAB 0000000000000000 003178a0
00000000000001d9 0000000000000000 0 0 1
[10] .dynstr STRTAB 0000000000717a80 00317a80
0000000000000215 0000000000000000 A 0 0 1
[11] .dynsym DYNSYM 0000000000717ca0 00317ca0
0000000000000378 0000000000000018 A 10 1 8
[12] .typelink PROGBITS 0000000000718020 00318020
0000000000001438 0000000000000000 A 0 0 32
[13] .itablink PROGBITS 0000000000719460 00319460
0000000000000808 0000000000000000 A 0 0 32
[14] .gosymtab PROGBITS 0000000000719c68 00319c68
0000000000000000 0000000000000000 A 0 0 1
[15] .gopclntab PROGBITS 0000000000719c80 00319c80
000000000013ce70 0000000000000000 A 0 0 32
[16] .go.buildinfo PROGBITS 0000000000857000 00457000
0000000000000130 0000000000000000 WA 0 0 16
[17] .got.plt PROGBITS 0000000000857140 00457140
0000000000000118 0000000000000008 WA 0 0 8
[18] .dynamic DYNAMIC 0000000000857260 00457260
0000000000000120 0000000000000010 WA 10 0 8
[19] .got PROGBITS 0000000000857380 00457380
0000000000000008 0000000000000008 WA 0 0 8
[20] .noptrdata PROGBITS 00000000008573a0 004573a0
000000000002fdf8 0000000000000000 WA 0 0 32
[21] .data PROGBITS 00000000008871a0 004871a0
000000000000bdb0 0000000000000000 WA 0 0 32
[22] .bss NOBITS 0000000000892f60 00492f60
00000000000300a0 0000000000000000 WA 0 0 32
[23] .noptrbss NOBITS 00000000008c3000 004c3000
000000000000def0 0000000000000000 WA 0 0 32
[24] .tbss NOBITS 0000000000000000 00000000
0000000000000008 0000000000000000 WAT 0 0 8
[25] .debug_abbrev PROGBITS 0000000000000000 00493000
0000000000000133 0000000000000000 C 0 0 1
[26] .debug_line PROGBITS 0000000000000000 00493133
0000000000060588 0000000000000000 C 0 0 1
[27] .debug_frame PROGBITS 0000000000000000 004f36bb
000000000001349f 0000000000000000 C 0 0 1
[28] .debug_gdb_s[...] PROGBITS 0000000000000000 00506b5a
000000000000003a 0000000000000000 0 0 1
[29] .debug_info PROGBITS 0000000000000000 00506b94
00000000000a5f3a 0000000000000000 C 0 0 1
[30] .debug_loc PROGBITS 0000000000000000 005acace
0000000000080755 0000000000000000 C 0 0 1
[31] .debug_ranges PROGBITS 0000000000000000 0062d223
000000000002078b 0000000000000000 C 0 0 1
[32] .interp PROGBITS 0000000000400fe4 00000fe4
000000000000001c 0000000000000000 A 0 0 1
[33] .note.go.buildid NOTE 0000000000400f80 00000f80
0000000000000064 0000000000000000 A 0 0 4
[34] .symtab SYMTAB 0000000000000000 0064d9b0
00000000000272b8 0000000000000018 35 277 8
[35] .strtab STRTAB 0000000000000000 00674c68
0000000000030483 0000000000000000 0 0 1
|
在上面readelf读出的section中,其中.go.buildinfo
包含了一些Go编译时的信息,比如Go的版本号、使用的module等,使用objdump查看.go.buildinfo
的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| objdump -s -j .go.buildinfo https_server
https_server: 文件格式 elf64-x86-64
Contents of section .go.buildinfo:
857000 ff20476f 20627569 6c64696e 663a0802 . Go buildinf:..
857010 00000000 00000000 00000000 00000000 ................
857020 08676f31 2e32302e 37fa0130 77af0c92 .go1.20.7..0w...
857030 74080241 e1c107e6 d618e670 61746809 t..A.......path.
857040 636f6d6d 616e642d 6c696e65 2d617267 command-line-arg
857050 756d656e 74730a62 75696c64 092d6275 uments.build.-bu
857060 696c646d 6f64653d 6578650a 6275696c ildmode=exe.buil
857070 64092d63 6f6d7069 6c65723d 67630a62 d.-compiler=gc.b
857080 75696c64 0943474f 5f454e41 424c4544 uild.CGO_ENABLED
857090 3d310a62 75696c64 0943474f 5f43464c =1.build.CGO_CFL
8570a0 4147533d 0a627569 6c640943 474f5f43 AGS=.build.CGO_C
8570b0 5050464c 4147533d 0a627569 6c640943 PPFLAGS=.build.C
8570c0 474f5f43 5858464c 4147533d 0a627569 GO_CXXFLAGS=.bui
8570d0 6c640943 474f5f4c 44464c41 47533d0a ld.CGO_LDFLAGS=.
8570e0 6275696c 6409474f 41524348 3d616d64 build.GOARCH=amd
8570f0 36340a62 75696c64 09474f4f 533d6c69 64.build.GOOS=li
857100 6e75780a 6275696c 6409474f 414d4436 nux.build.GOAMD6
857110 343d7631 0af93243 31861820 72008242 4=v1..2C1.. r..B
|
其中\xff Go buildinf:
是magic信息,共14个字节,在对应的Go源码中可以看到:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // https://github.com/golang/go/blob/2f6a25f4478905db5e169019bf9dc39ab2a50f89/src/debug/buildinfo/buildinfo.go#L45-L49
// The build info blob left by the linker is identified by
// a 16-byte header, consisting of buildInfoMagic (14 bytes),
// the binary's pointer size (1 byte),
// and whether the binary is big endian (1 byte).
buildInfoMagic = []byte("\xff Go buildinf:")
// 数据解析见: src/debug/buildinfo/buildinfo.go#L180-L189
// Decode the blob.
// The first 14 bytes are buildInfoMagic.
// The next two bytes indicate pointer size in bytes (4 or 8) and endianness
// (0 for little, 1 for big).
// Two virtual addresses to Go strings follow that: runtime.buildVersion,
// and runtime.modinfo.
// On 32-bit platforms, the last 8 bytes are unused.
// If the endianness has the 2 bit set, then the pointers are zero
// and the 32-byte header is followed by varint-prefixed string data
// for the two string values we care about.
|
继续看.go.buildifo
的内容:
![](/.read-infos-from-go-binary.assets/c8c0efcffec4fc1f24a7fc6fdacfa28eeb01fa39.png)
其实,翻看Go的源码可以发现,版本号信息其实就是runtime.buildVersion
这个对象。所以,其实上述的步骤等价于:
1
2
3
4
5
| # Go1.16
# 也就是说其实17个字节拿到的是runtime.buildVersion对象的指针
# 然后读出该对象指向的内容
readelf -s https_server|grep -i 'runtime.buildVersion'
5025: 0000000000870260 16 OBJECT GLOBAL DEFAULT 21 runtime.buildVersion
|
后·总结
所以,从Go的二进制文件读取版本号信息的思路可以是:
尾·链接
What Is an ELF File? | Baeldung on Linux
从 Go 的二进制文件中获取其依赖的模块信息 - 知乎 (zhihu.com)
Release History - The Go Programming Language (google.cn)
All releases - The Go Programming Language (google.cn)