go編譯可執行文件,go語言編譯工具

場景

公司線上運行的Go服務存在多個版本

時間:某天凌晨

事情:線上Go服務突然間 crash

緊急處理:重啟Go服務

故障排查:查詢日誌,找出可能出現的堆棧信息以及追溯源碼

問題:線上同時存在多個版本,如何知道當前 crash 的程序屬於哪個版本?

添加版本信息的兩種方案

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

package main

import (
	"flag"
	"fmt"
)
// 下面三個變量,每次發版都要修改
var version = "v0.0.1" // 程序版本號
var gitTag = "v0.0.1" // git tag 號
var dateTime = "2021-08-14 10:00:00" // 編譯生成時間

func main() {
	debugVerInfo := flag.Bool("ver", false, "show app version info")
	flag.Parse()

	if *debugVerInfo {
		fmt.Println("version is:", version)
		fmt.Println("dateTime is:", dateTime)
		fmt.Println("gitTag is:", gitTag)
		return
	}

	fmt.Println("do other thing")
}

由於手動在代碼中添加版本信息,所以在排查時可以查看到對應信息。

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

分析:

在很多公司甚至開源項目都會採用該方式,在代碼中顯式地添加版本等信息。

  1. 假設不經常發版或者發版周期比較長,則完全沒問題
  2. 假設發版頻繁,很大概率會出現版本信息的遺漏、錯誤
  3. 假設版本信息忘記更改,則查詢出來的信息就是錯的

針對以上情況,提出一個問題:Go是編譯型語言,版本等信息是否可以在編譯時,自動地打包到二進制文件中?

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

package main

import (
	"flag"
	"fmt"
)

var version = "v0.0.0"// 此處暫時只填寫大的版本號
var gitTag string
var dateTime string

func main() {
	debugVerInfo := flag.Bool("ver", false, "show app version info")
	flag.Parse()

	if *debugVerInfo {
		fmt.Println("version is:", version)
		fmt.Println("dateTime is:", dateTime)
		fmt.Println("gitTag is:", gitTag)
		return
	}

	fmt.Println("do other thing")
}

在編譯時,打包版本等信息到Go的二進制文件中:

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

build 通過 -ldflags 的 -X 參數可以在編譯時將值寫入變量

變量格式:包名稱.變量名稱=值

查看版本信息

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

優點:

  1. 無需代碼中顯式添加版本等信息
  2. 避免手動添加版本信息時,遺漏或者錯誤等情況發生
  3. 可使用持續集成工具自動把版本等信息打包到二進制文件中

原理

二進制文件在加載到內存中之後,整個內存空間會被劃分為若干段。除了代碼區、數據區、堆、棧,還有有一個段為符號表。

在編譯時,把版本等信息打包到符號表中,供程序運行時使用。

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

使用 readelf -s命令查看編譯好的Go二進制文件符號表信息,可以明顯看到在編譯時寫入的三個變量。

其中,main.version、main.gitTag、main.dateTime 大小都為16,是指 在Go中的string類型結構體大小。

(gdb) ptype version
type = struct string {
    uint8 *str;
    int len;
}

(gdb) ptype dateTime
type = struct string {
    uint8 *str;
    int len;
}

(gdb) ptype gitTag
type = struct string {
    uint8 *str;
    int len;
}

不知細心的你是否發現,在符號表顯示的變量具體值 main.version.str、main.dateTime.str、main.gitTag.str長度都比實際多一個位元組。

雖然目前Go實現了自舉,但是編譯Go編譯器的編譯器還是用C語言寫的

C語言字符串(位元組數組)是非安全類型,使用尾零來標識字符串結束。其中,尾零也佔用一個位元組。

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

(gdb) p version
$1 = "v0.0.1"

(gdb) p dateTime
$2 = "2021-08-13,23:26:44"

(gdb) p gitTag
$3 = "v0.0.1"

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/233373.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-11 13:31
下一篇 2024-12-11 13:31

相關推薦

發表回復

登錄後才能評論