Go编译时写入数据的原理

开发 后端
在很多公司甚至开源项目都会采用该方式,在代码中显式地添加版本等信息。Go是编译型语言,版本等信息是否可以在编译时,自动地打包到二进制文件中?

[[417666]]

场景

公司线上运行的Go服务存在多个版本

时间:某天凌晨

事情:线上Go服务突然间 crash

紧急处理:重启Go服务

故障排查:查询日志,找出可能出现的堆栈信息以及追溯源码

问题:线上同时存在多个版本,如何知道当前 crash 的程序属于哪个版本?

添加版本信息的两种方案

方案1,手动添加版本信息:

  1. package main 
  2.  
  3. import ( 
  4.     "flag" 
  5.     "fmt" 
  6. // 下面三个变量,每次发版都要修改 
  7. var version = "v0.0.1" // 程序版本号 
  8. var gitTag = "v0.0.1" // git tag 号 
  9. var dateTime = "2021-08-14 10:00:00" // 编译生成时间 
  10.  
  11. func main() { 
  12.     debugVerInfo := flag.Bool("ver"false"show app version info"
  13.     flag.Parse() 
  14.  
  15.     if *debugVerInfo { 
  16.         fmt.Println("version is:", version) 
  17.         fmt.Println("dateTime is:", dateTime) 
  18.         fmt.Println("gitTag is:", gitTag) 
  19.         return 
  20.     } 
  21.  
  22.     fmt.Println("do other thing"

由于手动在代码中添加版本信息,所以在排查时可以查看到对应信息。

  1. ➜  code ✗ ./client -ver   
  2. version is: v0.0.1 
  3. dateTime is: 2021-08-14 10:00:00 
  4. gitTag is: v0.0.1 

分析:

在很多公司甚至开源项目都会采用该方式,在代码中显式地添加版本等信息。

  1. 假设不经常发版或者发版周期比较长,则完全没问题
  2. 假设发版频繁,很大概率会出现版本信息的遗漏、错误
  3. 假设版本信息忘记更改,则查询出来的信息就是错的

针对以上情况,提出一个问题:Go是编译型语言,版本等信息是否可以在编译时,自动地打包到二进制文件中?

方案2,自动打包版本信息:

  1. package main 
  2.  
  3. import ( 
  4.     "flag" 
  5.     "fmt" 
  6.  
  7. var version = "v0.0.0"// 此处暂时只填写大的版本号 
  8. var gitTag string 
  9. var dateTime string 
  10.  
  11. func main() { 
  12.     debugVerInfo := flag.Bool("ver"false"show app version info"
  13.     flag.Parse() 
  14.  
  15.     if *debugVerInfo { 
  16.         fmt.Println("version is:", version) 
  17.         fmt.Println("dateTime is:", dateTime) 
  18.         fmt.Println("gitTag is:", gitTag) 
  19.         return 
  20.     } 
  21.  
  22.     fmt.Println("do other thing"

 在编译时,打包版本等信息到Go的二进制文件中:

  1. go build -ldflags \ 
  2.     "-X main.version=v0.0.1 -X main.dateTime=`date +%Y-%m-%d,%H:%M:%S` -X main.gitTag=`git tag`" \ 
  3.   -o client 

build 通过 -ldflags 的 -X 参数可以在编译时将值写入变量

变量格式:包名称.变量名称=值

查看版本信息

  1. ➜  code ✗ ./client -ver   
  2. version is: v0.0.1 
  3. dateTime is: 2021-08-14 10:00:00 
  4. gitTag is: v0.0.1 

优点:

无需代码中显式添加版本等信息

避免手动添加版本信息时,遗漏或者错误等情况发生

可使用持续集成工具自动把版本等信息打包到二进制文件中

原理

二进制文件在加载到内存中之后,整个内存空间会被划分为若干段。除了代码区、数据区、堆、栈,还有有一个段为符号表。

在编译时,把版本等信息打包到符号表中,供程序运行时使用。

  1. [root@localhost demo]# readelf -s client | grep main 
  2.     ...... 
  3.   1686: 00000000005608b0    16 OBJECT  GLOBAL DEFAULT   10 main.version 
  4.   1687: 00000000005608a0    16 OBJECT  GLOBAL DEFAULT   10 main.gitTag 
  5.   1688: 0000000000560890    16 OBJECT  GLOBAL DEFAULT   10 main.dateTime 
  6.     ...... 
  7.   2320: 00000000004eb2e8     7 OBJECT  GLOBAL DEFAULT    2 main.version.str 
  8.   2321: 00000000004ebba0    20 OBJECT  GLOBAL DEFAULT    2 main.dateTime.str 
  9.   2322: 00000000004eb2e0     7 OBJECT  GLOBAL DEFAULT    2 main.gitTag.str 

使用 readelf -s命令查看编译好的Go二进制文件符号表信息,可以明显看到在编译时写入的三个变量。

其中,main.version、main.gitTag、main.dateTime 大小都为16,是指 在Go中的string类型结构体大小。

  1. (gdb) ptype version 
  2. type = struct string { 
  3.     uint8 *str; 
  4.     int len; 
  5.  
  6. (gdb) ptype dateTime 
  7. type = struct string { 
  8.     uint8 *str; 
  9.     int len; 
  10.  
  11. (gdb) ptype gitTag 
  12. type = struct string { 
  13.     uint8 *str; 
  14.     int len; 

不知细心的你是否发现,在符号表显示的变量具体值 main.version.str、main.dateTime.str、main.gitTag.str长度都比实际多一个字节。

虽然目前Go实现了自举,但是编译Go编译器的编译器还是用C语言写的

C语言字符串(字节数组)是非安全类型,使用尾零来标识字符串结束。其中,尾零也占用一个字节。

尾零是 ASCII 第一个元素 0, 即:NUL

  1. (gdb) p version 
  2. $1 = "v0.0.1" 
  3.  
  4. (gdb) p dateTime 
  5. $2 = "2021-08-13,23:26:44" 
  6.  
  7. (gdb) p gitTag 
  8. $3 = "v0.0.1" 

 

责任编辑:姜华 来源: 今日头条
相关推荐

2020-09-14 08:56:30

Vue模板

2017-04-11 08:36:09

iOS编译应用

2023-05-08 08:05:42

内核模块Linux

2019-12-06 13:59:37

代码开发Python

2010-01-15 10:16:50

CentOS rpm安

2021-10-26 13:18:52

Go底层函数

2022-12-30 20:41:15

编译原理case

2013-12-30 11:21:31

Go编译器

2021-05-13 18:53:34

Go编译器Uber

2021-10-20 06:47:50

Elasticsear场景引擎

2021-08-02 15:02:37

Go Excelize 开发

2023-12-25 07:46:01

Go语言循环

2017-05-26 11:07:17

Android框架代码

2021-07-28 07:53:21

Go语言拷贝

2021-03-16 08:56:35

Go interface面试

2023-03-09 11:32:00

MongoDB数据策略

2021-06-07 23:51:16

MacGo服务

2021-11-30 06:56:58

编译Go程序

2011-07-04 10:56:10

Qt 移植 编译

2022-10-24 00:48:58

Go语言errgroup
点赞
收藏

51CTO技术栈公众号