1. 程式人生 > >減少 golang 二進位制檔案大小

減少 golang 二進位制檔案大小

文章目錄


環境:

$ go version
go version go1.11.2 linux/amd64

$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609

一. Go VS C 二進位制

hello.go

package main

import "fmt"
func main() { fmt.Println("hello world") }

hello.c

#include <stdio.h>

int main() {
    printf("hello world\n");
    return 0;
}
$ go build -o hello hello.go
$ go build -ldflags "-s -w" -o hello2 hello.go
$ gcc hello.c
$ ls -l
-rwxrwxr-x 1 zengxl zengxl 1902849 11月 27 15:40 hello
-rwxrwxr-x 1 zengxl zengxl 1353824 11月 27 15:43 hello2
-rwxrwxr-x 1 zengxl zengxl 8600    11月 27 15:44 a.out

golang 連線的引數:

$ go tool link -h

usage: link [options] main.o
-s	disable symbol table      # 去掉符號表
-w	disable DWARF generation  # 去掉除錯資訊

ELF

先來看下 C 的:

$ readelf -h a.out 
ELF 頭:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  類別:                              ELF64
  資料:                              2 補碼,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  型別:                              EXEC (可執行檔案)
  系統架構:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口點地址:               0x400430
  程式頭起點:          64 (bytes into file)
  Start of section headers:          6616 (bytes into file)
  標誌:             0x0
  本頭的大小:       64 (位元組)
  程式頭大小:       56 (位元組)
  Number of program headers:         9
  節頭大小:         64 (位元組)
  節頭數量:         31
  字串表索引節頭: 28
$ readelf -d a.out 

Dynamic section at offset 0xe28 contains 24 entries:
  標記        型別                         名稱/值
 0x0000000000000001 (NEEDED)             共享庫:[libc.so.6]
 0x000000000000000c (INIT)               0x4003c8
 0x000000000000000d (FINI)               0x4005b4
 0x0000000000000019 (INIT_ARRAY)         0x600e10
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e18
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x400318
 0x0000000000000006 (SYMTAB)             0x4002b8
 0x000000000000000a (STRSZ)              61 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400398
 0x0000000000000007 (RELA)               0x400380
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400360
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x400356
 0x0000000000000000 (NULL)               0x0

再來看下 go 的:

$ readelf -h hello
ELF 頭:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  類別:                              ELF64
  資料:                              2 補碼,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  型別:                              EXEC (可執行檔案)
  系統架構:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口點地址:               0x451fa0
  程式頭起點:          64 (bytes into file)
  Start of section headers:          456 (bytes into file)
  標誌:             0x0
  本頭的大小:       64 (位元組)
  程式頭大小:       56 (位元組)
  Number of program headers:         7
  節頭大小:         64 (位元組)
  節頭數量:         13
  字串表索引節頭: 3
$ readelf -d hello

There is no dynamic section in this file.

The linker in the gc toolchain creates statically-linked binaries by default. All Go binaries therefore include the Go runtime, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.

A simple C “hello, world” program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf weighs a couple of megabytes, but that includes more powerful run-time support and type and debugging information.

所以,為什麼 go 二進位制比 C 大很多就比較明顯了。

golang 靜態編譯,不依賴動態庫。

二. 如何減小 go 二進位制檔案大小

2.1. -ldflags

上面已經提到了過了。

$ go build -ldflags "-s -w" xxx.go

2.2. UPX

https://github.com/upx/upx

Commands:
  -1     compress faster                   -9    compress better
  -d     decompress                        -l    list compressed file
  -t     test compressed file              -V    display version number
  -h     give more help                    -L    display software license
Options:
  -q     be quiet                          -v    be verbose
  -oFILE write output to 'FILE'
  -f     force compression of suspicious files
  -k     keep backup files
file..   executables to (de)compress

Compression tuning options:
  --brute             try all available compression methods & filters [slow]
  --ultra-brute       try even more compression variants [very slow]
$ upx --brute binaryfile

IDA 逆向分析簡單看下:

https://www.hex-rays.com/products/ida/support/download.shtml

下面是支援 Go 的 IDA helper

https://github.com/sibears/IDAGolangHelper

原始的 go 二進位制檔案:
可以看到 go 的一些函式名。
hello

去掉符號表和除錯資訊的 go 二進位制檔案:
已經看不到函式名資訊,只有類似 sub_47BF70 這樣。
在這裡插入圖片描述

經過 upx 壓縮的 go 二進位制檔案:
資訊已經比較少了,入口點也發生了變化。
在這裡插入圖片描述

參考

https://stackoverflow.com/questions/3861634/how-to-reduce-compiled-file-size

https://golang.org/doc/faq#Why_is_my_trivial_program_such_a_large_binary

https://www.cnxct.com/why-golang-elf-binary-file-is-large-than-c/