unless’s blog

日々のちょっとした技術的なことの羅列

Goのhello worldを小さくする

これはなに?

ふとGolangのbinaryを小さく出来ないかなと思い立ったのでhello worldをスリムにしていこうと思う

hello world

Golangでのhello worldはこちら

package main

import "fmt"

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

普通にbuild

これを普通にbuildするとどうなるか

# go build hello.go
# ll hello
-rwxr-xr-x 1 root root 2008801 Feb 20 02:01 hello*
# ll -h hello
-rwxr-xr-x 1 root root 2.0M Feb 20 02:01 hello*

2MBもあるようだ

-ldflags

とりあえずldflagsをつけてbuildしてみる
-sはデバッグのために使われるシンボルテーブルを生成しないようにする

# go build -o hello_ld -ldflags="-s" hello.go
# ll hello_ld
-rwxr-xr-x 1 root root 1433600 Feb 20 02:05 hello_ld*
# ll -h hello_ld
-rwxr-xr-x 1 root root 1.4M Feb 20 02:05 hello_ld*

だいぶ削れた
けどまだ削れる気がする

elfファイルを見てみる

とりあえず消せそうなsectionあるか見てみる

# readelf -S hello_ld
There are 14 section headers, starting at offset 0x1c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       000000000008bf19  0000000000000000  AX       0     0     16
  [ 2] .rodata           PROGBITS         000000000048d000  0008d000
       000000000004f550  0000000000000000   A       0     0     32
  [ 3] .shstrtab         STRTAB           0000000000000000  000dc560
       000000000000008a  0000000000000000           0     0     1
  [ 4] .typelink         PROGBITS         00000000004dc600  000dc600
       0000000000000c68  0000000000000000   A       0     0     32
  [ 5] .itablink         PROGBITS         00000000004dd268  000dd268
       0000000000000050  0000000000000000   A       0     0     8
  [ 6] .gosymtab         PROGBITS         00000000004dd2b8  000dd2b8
       0000000000000000  0000000000000000   A       0     0     1
  [ 7] .gopclntab        PROGBITS         00000000004dd2c0  000dd2c0
       000000000006b67c  0000000000000000   A       0     0     32
  [ 8] .go.buildinfo     PROGBITS         0000000000549000  00149000
       0000000000000020  0000000000000000  WA       0     0     16
  [ 9] .noptrdata        PROGBITS         0000000000549020  00149020
       000000000000d0d8  0000000000000000  WA       0     0     32
  [10] .data             PROGBITS         0000000000556100  00156100
       0000000000007050  0000000000000000  WA       0     0     32
  [11] .bss              NOBITS           000000000055d160  0015d160
       000000000001b870  0000000000000000  WA       0     0     32
  [12] .noptrbss         NOBITS           00000000005789e0  001789e0
       0000000000002768  0000000000000000  WA       0     0     32
  [13] .note.go.buildid  NOTE             0000000000400f9c  00000f9c
       0000000000000064  0000000000000000   A       0     0     4
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

.gosymtabとか.note.go.buildidは削れそうだけどスズメの涙だろうなと思いつつ削除

# readelf -x13 hello_ld

Hex dump of section '.note.go.buildid':
  0x00400f9c 04000000 53000000 04000000 476f0000 ....S.......Go..
  0x00400fac 6c656668 6e656c37 74716b75 72507254 lefhnel7tqkurPrT
  0x00400fbc 5335634a 2f794f50 62666478 494d4357 S5cJ/yOPbfdxIMCW
  0x00400fcc 6147766b 66435546 792f7a4f 78425866 aGvkfCUFy/zOxBXf
  0x00400fdc 4f7a632d 505f4d41 43306d56 675f2f6e Ozc-P_MAC0mVg_/n
  0x00400fec 6e50494a 6c6c532d 632d4443 6842386b nPIJllS-c-DChB8k
  0x00400ffc 674c4e00                            gLN.

# strip -R .gosymtab hello_ld
# strip -R .note.go.buildid hello_ld
# ./hello_ld
hello, world
# ll hello_ld
-rwxr-xr-x 1 root root 1430712 Feb 20 02:11 hello_ld*
# ll -h hello_ld
-rwxr-xr-x 1 root root 1.4M Feb 20 02:11 hello_ld*

これ以上むりそうに感じる...
upxを使えば削減できるだろうが負けた気分になるので他の手を使いたい

medium.com

fmt.Printlnやめればいいのでは?

syscallを直接よべばもう少し削減できるんじゃね?(なんの意味もないけど減らしたい気持ちになった)
と思ったのでsyscallを呼んでみることにした

golang.org

package main

import (
    "syscall"
    "unsafe"
)

func main() {
  p := []byte("hello, world\n")
  var _p0 unsafe.Pointer
  _p0 = unsafe.Pointer(&p[0])
  syscall.Syscall(syscall.SYS_WRITE, uintptr(1), uintptr(_p0), uintptr(len(p)))
}

buildしてみる

# go build -o hello_sys -ldflags='-s' hello_sys.go
# ll hello_sys
-rwxr-xr-x 1 root root 851968 Feb 20 02:17 hello_sys*
# ll -h hello_sys
-rwxr-xr-x 1 root root 832K Feb 20 02:17 hello_sys*

めっさ小さい!

もうちょっとだけ悪あがき

# strip -R .gosymtab hello_sys
# strip -R .note.go.buildid hello_sys
# strip -s hello_sys

消せそうなのを消していく

# ll hello_sys
-rwxr-xr-x 1 root root 849880 Feb 20 02:19 hello_sys*
# ll -h hello_sys
-rwxr-xr-x 1 root root 830K Feb 20 02:19 hello_sys*

個人的にはこれが限界かな
そもそもsyscallなんて普通呼ばないから意味もないし自己満足だけど
容量は減ってなんだかうれしくなったからいいか