大家好,我是张晋涛。
我们用 Go 构建的二进制文件中默认包含了很多有用的信息。例如,可以获取构建用的 Go 版本:
(这里我使用我一直参与的一个开源项目 KIND[1] 为例)
➜ kind git:(master) ✗ go version ./bin/kind
./bin/kind: go1.16
或者也可以获取该二进制所依赖的模块信息:
➜ kind git:(master) ✗ go version -m ./bin/kind
./bin/kind: go1.16
path sigs.k8s.io/kind
mod sigs.k8s.io/kind (devel)
dep github.com/BurntSushi/toml v0.3.1
dep github.com/alessio/shellescape v1.4.1
dep github.com/evanphx/json-patch/v5 v5.2.0
dep github.com/mattn/go-isatty v0.0.12
dep github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
dep github.com/pkg/errors v0.9.1
dep github.com/spf13/cobra v1.1.1
dep github.com/spf13/pflag v1.0.5
dep golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
dep gopkg.in/yaml.v2 v2.2.8
dep gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
dep k8s.io/apimachinery v0.20.2
dep sigs.k8s.io/yaml v1.2.0
查看 KIND 代码仓库中的 go.mod文件,都包含在内了。
其实 Linux 系统中二进制文件包含额外的信息并非 Go 所特有的,下面我将具体介绍其内部原理和实现。当然,用 Go 构建的二进制文件仍是本文的主角。
Linux ELF 格式
ELF 是 Executable and Linkable Format 的缩写,是一种用于可执行文件、目标文件、共享库和核心转储(core dump)的标准文件格式。ELF 文件 通常 是编译器之类的输出,并且是二进制格式。以 Go 编译出的可执行文件为例,我们使用 file 命令即可看到其具体类型 ELF 64-bit LSB executable
:
➜ kind git:(master) ✗ file ./bin/kind
./bin/kind: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
本文中我们来具体看看 64 位可执行文件使用的 ELF 文件格式的结构和 Linux 内核源码中对它的定义。
使用 ELF 文件格式的可执行文件是由 ELF 头(ELF Header) 开始,后跟 程序头(Program Header) 或 节头(Section Header) 或两者均有组成的。
ELF 头
ELF 头始终位于文件的零偏移(zero offset)处(即:起点位置),同时在 ELF 头中还定义了程序头和节头的偏移量。
我们可以通过 readelf 命令查看可执行文件的 ELF 头,如下:
➜ kind git:(master) ✗ readelf -h ./bin/kind
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x46c460
Start of program headers: 64 (bytes into file)
Start of section headers: 400 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 6
Size of section headers: 64 (bytes)
Number of section headers: 15
Section header string table index: 3
从上面的输出我们可以看到,ELF 头是以某个 Magic 开始的,此 Magic 标识了有关文件的信息,即:前四个 16 进制数,表示这是一个 ELF 文件。具体来说,将它们换算成其对应的 ASCII 码即可:
45 = E
4c = L
46 = F
7f 是其前缀,当然,也可以直接在 Linux 内核源码[2]