Making Go Go on a Diet

Recently I decided to look into the Go language - this was purely due to my inquisitive nature, and I thought it’d be fun to learn another language. However, there’s some stranege things going on when you compile the source.

I wrote a small piece of code that interacted with the Twitter API to get the URL of an image included in a tweet, it’s 267 lines long - filesize is not particularly big either - 6.3K.

1
2
3
4
5
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 16K
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:53 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go

The go build command,, by default will compile with Debug information, and Symbols. Additionally, it will include the Go runtime, so that the binary will execute in environments that do not have Go installed. Here’s the above sourcecode compiled with the default go build command

1
2
3
4
5
6
7
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ go build
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 6.9M
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:54 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 6.9M Jun 12 15:54 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go

6.9 MEG FROM A 6.3K SOURCE FILE ?! OK, so lets take a look at what it is and what it includes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ file twitter_put
twitter_put: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ readelf -S twitter_put
There are 33 section headers, starting at offset 0x238:

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         0000000000400c00  00000c00
       0000000000234d3f  0000000000000000  AX       0     0     16
  [ 2] .plt              PROGBITS         0000000000635940  00235940
       0000000000000170  0000000000000010  AX       0     0     4
  [ 3] .rodata           PROGBITS         0000000000636000  00236000
       000000000019e4c8  0000000000000000   A       0     0     32
  [ 4] .typelink         PROGBITS         00000000007d44c8  003d44c8
       0000000000001218  0000000000000000   A       0     0     8
  [ 5] .gosymtab         PROGBITS         00000000007d56e0  003d56e0
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000007d56e0  003d56e0
       00000000000d4858  0000000000000000   A       0     0     32
  [ 7] .rela             RELA             00000000008a9f40  004a9f40
       0000000000000018  0000000000000018   A      14     0     8
  [ 8] .gnu.version      VERSYM           00000000008a9f60  004a9f60
       0000000000000038  0000000000000002   A      14     0     2
  [ 9] .gnu.version_r    VERNEED          00000000008a9fa0  004a9fa0
       0000000000000040  0000000000000000   A      12     2     8
  [10] .hash             HASH             00000000008a9fe0  004a9fe0
       0000000000000090  0000000000000004   A      14     0     8
  [11] .shstrtab         STRTAB           0000000000000000  004aa080
       0000000000000185  0000000000000000           0     0     1
  [12] .dynstr           STRTAB           00000000008aa220  004aa220
       0000000000000189  0000000000000000   A       0     0     1
  [13] .rela.plt         RELA             00000000008aa3c0  004aa3c0
       0000000000000210  0000000000000018   A      14     2     8
  [14] .dynsym           DYNSYM           00000000008aa5e0  004aa5e0
       00000000000002a0  0000000000000018   A      12     0     8
  [15] .got              PROGBITS         00000000008ab000  004ab000
       0000000000000008  0000000000000008  WA       0     0     8
  [16] .got.plt          PROGBITS         00000000008ab020  004ab020
       00000000000000c8  0000000000000008  WA       0     0     8
  [17] .dynamic          DYNAMIC          00000000008ab100  004ab100
       0000000000000130  0000000000000010  WA      12     0     8
  [18] .noptrdata        PROGBITS         00000000008ab240  004ab240
       0000000000016b0c  0000000000000000  WA       0     0     32
  [19] .data             PROGBITS         00000000008c1d60  004c1d60
       0000000000007110  0000000000000000  WA       0     0     32
  [20] .bss              NOBITS           00000000008c8e80  004c8e80
       0000000000008ee0  0000000000000000  WA       0     0     32
  [21] .noptrbss         NOBITS           00000000008d1d60  004d1d60
       00000000000135c8  0000000000000000  WA       0     0     32
  [22] .interp           PROGBITS         0000000000400be4  00000be4
       000000000000001c  0000000000000000   A       0     0     1
  [23] .symtab           SYMTAB           0000000000000000  004c9000
       0000000000030b10  0000000000000018          24   197     8
  [24] .strtab           STRTAB           0000000000000000  004f9b10
       0000000000035eab  0000000000000000           0     0     1
  [25] .tbss             NOBITS           0000000000000000  00000000
       0000000000000010  0000000000000000 WAT       0     0     8
  [26] .debug_abbrev     PROGBITS         0000000000000000  0052f9bb
       00000000000000fd  0000000000000000           0     0     1
  [27] .debug_line       PROGBITS         0000000000000000  0052fab8
       000000000002f2d9  0000000000000000           0     0     1
  [28] .debug_frame      PROGBITS         0000000000000000  0055ed91
       000000000003952c  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  005982bd
       00000000000e6988  0000000000000000           0     0     1
  [30] .debug_pubnames   PROGBITS         0000000000000000  0067ec45
       000000000003d502  0000000000000000           0     0     1
  [31] .debug_pubtypes   PROGBITS         0000000000000000  006bc147
       0000000000017b10  0000000000000000           0     0     1
  [32] .debug_aranges    PROGBITS         0000000000000000  006d3c57
       0000000000000030  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

So, debug information, symbols etc. It is possible to remove all the DWARF information using go build -ldflags '-w' which will create us a slightly smaller binary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ go build -ldflags '-w'
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 5.3M
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:55 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 5.2M Jun 12 15:55 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ file twitter_put
twitter_put: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ readelf -S twitter_put
There are 26 section headers, starting at offset 0x238:

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         0000000000400c00  00000c00
       0000000000234d3f  0000000000000000  AX       0     0     16
  [ 2] .plt              PROGBITS         0000000000635940  00235940
       0000000000000170  0000000000000010  AX       0     0     4
  [ 3] .rodata           PROGBITS         0000000000636000  00236000
       000000000019e4c8  0000000000000000   A       0     0     32
  [ 4] .typelink         PROGBITS         00000000007d44c8  003d44c8
       0000000000001218  0000000000000000   A       0     0     8
  [ 5] .gosymtab         PROGBITS         00000000007d56e0  003d56e0
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000007d56e0  003d56e0
       00000000000d4858  0000000000000000   A       0     0     32
  [ 7] .rela             RELA             00000000008a9f40  004a9f40
       0000000000000018  0000000000000018   A      14     0     8
  [ 8] .gnu.version      VERSYM           00000000008a9f60  004a9f60
       0000000000000038  0000000000000002   A      14     0     2
  [ 9] .gnu.version_r    VERNEED          00000000008a9fa0  004a9fa0
       0000000000000040  0000000000000000   A      12     2     8
  [10] .hash             HASH             00000000008a9fe0  004a9fe0
       0000000000000090  0000000000000004   A      14     0     8
  [11] .shstrtab         STRTAB           0000000000000000  004aa080
       00000000000000dd  0000000000000000           0     0     1
  [12] .dynstr           STRTAB           00000000008aa160  004aa160
       0000000000000189  0000000000000000   A       0     0     1
  [13] .rela.plt         RELA             00000000008aa300  004aa300
       0000000000000210  0000000000000018   A      14     2     8
  [14] .dynsym           DYNSYM           00000000008aa520  004aa520
       00000000000002a0  0000000000000018   A      12     0     8
  [15] .got              PROGBITS         00000000008ab000  004ab000
       0000000000000008  0000000000000008  WA       0     0     8
  [16] .got.plt          PROGBITS         00000000008ab020  004ab020
       00000000000000c8  0000000000000008  WA       0     0     8
  [17] .dynamic          DYNAMIC          00000000008ab100  004ab100
       0000000000000130  0000000000000010  WA      12     0     8
  [18] .noptrdata        PROGBITS         00000000008ab240  004ab240
       0000000000016b0c  0000000000000000  WA       0     0     32
  [19] .data             PROGBITS         00000000008c1d60  004c1d60
       0000000000007110  0000000000000000  WA       0     0     32
  [20] .bss              NOBITS           00000000008c8e80  004c8e80
       0000000000008ee0  0000000000000000  WA       0     0     32
  [21] .noptrbss         NOBITS           00000000008d1d60  004d1d60
       00000000000135c8  0000000000000000  WA       0     0     32
  [22] .interp           PROGBITS         0000000000400be4  00000be4
       000000000000001c  0000000000000000   A       0     0     1
  [23] .symtab           SYMTAB           0000000000000000  004c9000
       0000000000030b10  0000000000000018          24   197     8
  [24] .strtab           STRTAB           0000000000000000  004f9b10
       0000000000035eab  0000000000000000           0     0     1
  [25] .tbss             NOBITS           0000000000000000  00000000
       0000000000000010  0000000000000000 WAT       0     0     8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Now we’re down to around 5M, with no debug information, but the file is still quite big, so the next step is to remove the symbols when compiling with go build -ldflags '-w -s'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ go build -ldflags '-w -s'
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 4.9M
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:56 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 4.8M Jun 12 15:56 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ file twitter_put
twitter_put: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), stripped
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ readelf -S twitter_put
There are 24 section headers, starting at offset 0x238:

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         0000000000400c00  00000c00
       0000000000234d3f  0000000000000000  AX       0     0     16
  [ 2] .plt              PROGBITS         0000000000635940  00235940
       0000000000000170  0000000000000010  AX       0     0     4
  [ 3] .rodata           PROGBITS         0000000000636000  00236000
       000000000019e4c8  0000000000000000   A       0     0     32
  [ 4] .typelink         PROGBITS         00000000007d44c8  003d44c8
       0000000000001218  0000000000000000   A       0     0     8
  [ 5] .gosymtab         PROGBITS         00000000007d56e0  003d56e0
       0000000000000000  0000000000000000   A       0     0     1
  [ 6] .gopclntab        PROGBITS         00000000007d56e0  003d56e0
       00000000000d4858  0000000000000000   A       0     0     32
  [ 7] .rela             RELA             00000000008a9f40  004a9f40
       0000000000000018  0000000000000018   A      14     0     8
  [ 8] .gnu.version      VERSYM           00000000008a9f60  004a9f60
       0000000000000038  0000000000000002   A      14     0     2
  [ 9] .gnu.version_r    VERNEED          00000000008a9fa0  004a9fa0
       0000000000000040  0000000000000000   A      12     2     8
  [10] .hash             HASH             00000000008a9fe0  004a9fe0
       0000000000000090  0000000000000004   A      14     0     8
  [11] .shstrtab         STRTAB           0000000000000000  004aa080
       00000000000000cd  0000000000000000           0     0     1
  [12] .dynstr           STRTAB           00000000008aa160  004aa160
       0000000000000189  0000000000000000   A       0     0     1
  [13] .rela.plt         RELA             00000000008aa300  004aa300
       0000000000000210  0000000000000018   A      14     2     8
  [14] .dynsym           DYNSYM           00000000008aa520  004aa520
       00000000000002a0  0000000000000018   A      12     0     8
  [15] .got              PROGBITS         00000000008ab000  004ab000
       0000000000000008  0000000000000008  WA       0     0     8
  [16] .got.plt          PROGBITS         00000000008ab020  004ab020
       00000000000000c8  0000000000000008  WA       0     0     8
  [17] .dynamic          DYNAMIC          00000000008ab100  004ab100
       0000000000000130  0000000000000010  WA      12     0     8
  [18] .noptrdata        PROGBITS         00000000008ab240  004ab240
       0000000000016b0c  0000000000000000  WA       0     0     32
  [19] .data             PROGBITS         00000000008c1d60  004c1d60
       0000000000007110  0000000000000000  WA       0     0     32
  [20] .bss              NOBITS           00000000008c8e80  004c8e80
       0000000000008ee0  0000000000000000  WA       0     0     32
  [21] .noptrbss         NOBITS           00000000008d1d60  004d1d60
       00000000000135c8  0000000000000000  WA       0     0     32
  [22] .interp           PROGBITS         0000000000400be4  00000be4
       000000000000001c  0000000000000000   A       0     0     1
  [23] .tbss             NOBITS           0000000000000000  00000000
       0000000000000010  0000000000000000 WAT       0     0     8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

We’re getting somewhere, but the issue here is because the Go runtime is included inside every compiled Go binary. If you plan on using the binary within environments that do not have the Go runtime installed, this is about as small as you could natively make this binary (without packing, of course - we’ll get to that later). If you plan on only using the binary where the Go runtime is installed, we can use gccgo to compile the source, and omit the inclusion of the runtime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ go build -compiler gccgo
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 736K
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:57 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 718K Jun 12 15:57 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ file twitter_put
twitter_put: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=28beacf6033290377de9741650fdbb986863dcae, not stripped
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ readelf -S twitter_put
There are 42 section headers, starting at offset 0xb2990:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .dynsym           DYNSYM           0000000000400298  00000298
       0000000000006408  0000000000000018   A       5     1     8
  [ 5] .dynstr           STRTAB           00000000004066a0  000066a0
       0000000000013188  0000000000000000   A       0     0     1
  [ 6] .gnu.hash         GNU_HASH         0000000000419828  00019828
       000000000000197c  0000000000000000   A       4     0     8
  [ 7] .gnu.version      VERSYM           000000000041b1a4  0001b1a4
       0000000000000856  0000000000000002   A       4     0     2
  [ 8] .gnu.version_r    VERNEED          000000000041b9fc  0001b9fc
       0000000000000090  0000000000000000   A       5     4     4
  [ 9] .rela.dyn         RELA             000000000041ba90  0001ba90
       0000000000000468  0000000000000018   A       4     0     8
  [10] .rela.plt         RELA             000000000041bef8  0001bef8
       00000000000015c0  0000000000000018  AI       4    12     8
  [11] .init             PROGBITS         000000000041d4b8  0001d4b8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         000000000041d4e0  0001d4e0
       0000000000000e90  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         000000000041e370  0001e370
       0000000000017122  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000435494  00035494
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         00000000004354a0  000354a0
       000000000001bde7  0000000000000000   A       0     0     32
  [16] .gcc_except_table PROGBITS         0000000000451288  00051288
       0000000000000171  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000451400  00051400
       0000000000002404  0000000000000000   A       0     0     8
  [18] .eh_frame_hdr     PROGBITS         0000000000453804  00053804
       00000000000006e4  0000000000000000   A       0     0     4
  [19] .tbss             NOBITS           0000000000454000  00054000
       00000000000000c8  0000000000000000 WAT       0     0     8
  [20] .dynamic          DYNAMIC          0000000000454000  00054000
       0000000000000220  0000000000000010  WA       5     0     8
  [21] .got              PROGBITS         0000000000454220  00054220
       0000000000000010  0000000000000000  WA       0     0     8
  [22] .got.plt          PROGBITS         0000000000454230  00054230
       0000000000000758  0000000000000000  WA       0     0     8
  [23] .data             PROGBITS         00000000004549a0  000549a0
       0000000000005a20  0000000000000000  WA       0     0     32
  [24] .jcr              PROGBITS         000000000045a3c0  0005a3c0
       0000000000000008  0000000000000000  WA       0     0     8
  [25] .tm_clone_table   PROGBITS         000000000045a3c8  0005a3c8
       0000000000000000  0000000000000000  WA       0     0     8
  [26] .fini_array       FINI_ARRAY       000000000045a3c8  0005a3c8
       0000000000000008  0000000000000000  WA       0     0     8
  [27] .init_array       INIT_ARRAY       000000000045a3d0  0005a3d0
       0000000000000018  0000000000000000  WA       0     0     8
  [28] .bss              NOBITS           000000000045a400  0005a400
       00000000000009d0  0000000000000000  WA       0     0     32
  [29] .comment          PROGBITS         0000000000000000  0005a3e8
       0000000000000080  0000000000000001  MS       0     0     1
  [30] .go_export        PROGBITS         0000000000000000  0005a468
       0000000000016635  0000000000000000           0     0     1
  [31] .debug_info       PROGBITS         0000000000000000  00070a9d
       000000000000dbf1  0000000000000000           0     0     1
  [32] .debug_abbrev     PROGBITS         0000000000000000  0007e68e
       0000000000000eb9  0000000000000000           0     0     1
  [33] .debug_aranges    PROGBITS         0000000000000000  0007f550
       0000000000000150  0000000000000000           0     0     16
  [34] .debug_ranges     PROGBITS         0000000000000000  0007f6a0
       00000000000006d0  0000000000000000           0     0     1
  [35] .debug_line       PROGBITS         0000000000000000  0007fd70
       000000000000223e  0000000000000000           0     0     1
  [36] .debug_str        PROGBITS         0000000000000000  00081fae
       000000000000878f  0000000000000001  MS       0     0     1
  [37] .debug_loc        PROGBITS         0000000000000000  0008a73d
       0000000000002e6d  0000000000000000           0     0     1
  [38] .note.gnu.gold-ve NOTE             0000000000000000  0008d5ac
       000000000000001c  0000000000000000           0     0     4
  [39] .symtab           SYMTAB           0000000000000000  0008d5c8
       000000000000f948  0000000000000018          40   1593     8
  [40] .strtab           STRTAB           0000000000000000  0009cf10
       00000000000158d3  0000000000000000           0     0     1
  [41] .shstrtab         STRTAB           0000000000000000  000b27e3
       00000000000001ab  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

We can get even smaller by combining everything we’ve learnt above, substituting -ldflags with -gccgoflags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ go build -gccgoflags '-w -s' -compiler gccgo
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 472K
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:57 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 454K Jun 12 15:57 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ file twitter_put
twitter_put: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0eed286d69c0b506f591058510c789d8574dbd13, stripped
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ readelf -S twitter_put
There are 33 section headers, starting at offset 0x70c00:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .dynsym           DYNSYM           0000000000400298  00000298
       0000000000006408  0000000000000018   A       5     1     8
  [ 5] .dynstr           STRTAB           00000000004066a0  000066a0
       0000000000013188  0000000000000000   A       0     0     1
  [ 6] .gnu.hash         GNU_HASH         0000000000419828  00019828
       000000000000197c  0000000000000000   A       4     0     8
  [ 7] .gnu.version      VERSYM           000000000041b1a4  0001b1a4
       0000000000000856  0000000000000002   A       4     0     2
  [ 8] .gnu.version_r    VERNEED          000000000041b9fc  0001b9fc
       0000000000000090  0000000000000000   A       5     4     4
  [ 9] .rela.dyn         RELA             000000000041ba90  0001ba90
       0000000000000468  0000000000000018   A       4     0     8
  [10] .rela.plt         RELA             000000000041bef8  0001bef8
       00000000000015c0  0000000000000018  AI       4    12     8
  [11] .init             PROGBITS         000000000041d4b8  0001d4b8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         000000000041d4e0  0001d4e0
       0000000000000e90  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         000000000041e370  0001e370
       0000000000017122  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000435494  00035494
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         00000000004354a0  000354a0
       000000000001bde7  0000000000000000   A       0     0     32
  [16] .gcc_except_table PROGBITS         0000000000451288  00051288
       0000000000000171  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000451400  00051400
       0000000000002404  0000000000000000   A       0     0     8
  [18] .eh_frame_hdr     PROGBITS         0000000000453804  00053804
       00000000000006e4  0000000000000000   A       0     0     4
  [19] .tbss             NOBITS           0000000000454000  00054000
       00000000000000c8  0000000000000000 WAT       0     0     8
  [20] .dynamic          DYNAMIC          0000000000454000  00054000
       0000000000000220  0000000000000010  WA       5     0     8
  [21] .got              PROGBITS         0000000000454220  00054220
       0000000000000010  0000000000000000  WA       0     0     8
  [22] .got.plt          PROGBITS         0000000000454230  00054230
       0000000000000758  0000000000000000  WA       0     0     8
  [23] .data             PROGBITS         00000000004549a0  000549a0
       0000000000005a20  0000000000000000  WA       0     0     32
  [24] .jcr              PROGBITS         000000000045a3c0  0005a3c0
       0000000000000008  0000000000000000  WA       0     0     8
  [25] .tm_clone_table   PROGBITS         000000000045a3c8  0005a3c8
       0000000000000000  0000000000000000  WA       0     0     8
  [26] .fini_array       FINI_ARRAY       000000000045a3c8  0005a3c8
       0000000000000008  0000000000000000  WA       0     0     8
  [27] .init_array       INIT_ARRAY       000000000045a3d0  0005a3d0
       0000000000000018  0000000000000000  WA       0     0     8
  [28] .bss              NOBITS           000000000045a400  0005a400
       00000000000009d0  0000000000000000  WA       0     0     32
  [29] .comment          PROGBITS         0000000000000000  0005a3e8
       0000000000000080  0000000000000001  MS       0     0     1
  [30] .go_export        PROGBITS         0000000000000000  0005a468
       0000000000016635  0000000000000000           0     0     1
  [31] .note.gnu.gold-ve NOTE             0000000000000000  00070aa0
       000000000000001c  0000000000000000           0     0     4
  [32] .shstrtab         STRTAB           0000000000000000  00070abc
       0000000000000142  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

So, now we have two binaries, one with the Go runtime, and one without (you can see the size difference).

1
2
3
4
5
6
7
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lah
total 5.3M
drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 15:59 .
drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
-rwxr-xr-x  1 recrudesce recrudesce 4.8M Jun 12 15:59 twitter_put
-rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
-rwxr-xr-x  1 recrudesce recrudesce 454K Jun 12 15:57 twitter_put_gccgo

To get these files smaller, the only real option here is to pack them with something like upx - the best compression can be obtained using the --ultra-brute -9 arguments. Here are the two files packed with upx. But, before we upx, we have to “fix” the files using goupx as you will experience a EOFException: premature end of file error when compressing. We also don’t want it to run the upx process, because we’re going to pass some custom parameters to it.

1
2
3
4
5
6
7
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ../github.com/pwaller/goupx/goupx twitter_put --no-upx
2015/06/12 16:01:22 {Class:ELFCLASS64 Data:ELFDATA2LSB Version:EV_CURRENT OSABI:ELFOSABI_NONE ABIVersion:0 ByteOrder:LittleEndian Type:ET_EXEC Machine:EM_X86_64 Entry:4438032}
2015/06/12 16:01:22 Hemming PT_LOAD section
2015/06/12 16:01:22 File fixed!
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ../github.com/pwaller/goupx/goupx twitter_put_gccgo --no-upx
2015/06/12 16:01:27 {Class:ELFCLASS64 Data:ELFDATA2LSB Version:EV_CURRENT OSABI:ELFOSABI_NONE ABIVersion:0 ByteOrder:LittleEndian Type:ET_EXEC Machine:EM_X86_64 Entry:4318160}
2015/06/12 16:01:27 File fixed!

Now to the packing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ upx --ultra-brute -9 ./twitter_put
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   5017216 ->   1013180   20.19%  linux/ElfAMD   twitter_put

Packed 1 file.
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ upx --ultra-brute -9 ./twitter_put_gccgo
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    463936 ->     82340   17.75%  linux/ElfAMD   twitter_put_gccgo

Packed 1 file.

This results in the following filesizes

1
2
3
4
5
6
7
recrudesce@senketsu:~/Documents/Learning/Go/src/twitter_put$ ls -lahls -lah
total 1.1M
4.0K drwxrwxr-x  2 recrudesce recrudesce 4.0K Jun 12 16:02 .
4.0K drwxrwxr-x 12 recrudesce recrudesce 4.0K Jun 12 15:53 ..
992K -rwxr-xr-x  1 recrudesce recrudesce 990K Jun 12 16:01 twitter_put
8.0K -rw-rw-r--  1 recrudesce recrudesce 6.3K Jun 12 15:53 twitter_put_2.go
 84K -rwxr-xr-x  1 recrudesce recrudesce  81K Jun 12 15:57 twitter_put_gccgo

Stripping and packing a Go binary in the above way is not recommended and does not guarantee that the binary will function afterwards - I have had about a 50/50 success rate (as an example, the binary shown here doesn’t work when compiled with gccgo, whereas the stripped and packed binary that includes the Go runtime continues to function perfectly. YMMV)

So, the lesson here is that you shouldn’t expect Go binaries to be small, and it’s not a good language to learn if you want small applications - use another language for that. Go is fun if you want to learn a new language for personal tools.