これはなに?
ふと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を使えば削減できるだろうが負けた気分になるので他の手を使いたい
fmt.Printlnやめればいいのでは?
syscallを直接よべばもう少し削減できるんじゃね?(なんの意味もないけど減らしたい気持ちになった)
と思ったのでsyscallを呼んでみることにした
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なんて普通呼ばないから意味もないし自己満足だけど
容量は減ってなんだかうれしくなったからいいか