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.
12345
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
1234567
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.
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
0000000000000000000000000000000000 0
[ 1] .text PROGBITS 0000000000400c00 00000c00
0000000000234d3f 0000000000000000 AX 00 16
[ 2] .plt PROGBITS 0000000000635940 00235940
00000000000001700000000000000010 AX 00 4
[ 3] .rodata PROGBITS 0000000000636000 00236000
000000000019e4c8 0000000000000000 A 00 32
[ 4] .typelink PROGBITS 00000000007d44c8 003d44c8
00000000000012180000000000000000 A 00 8
[ 5] .gosymtab PROGBITS 00000000007d56e0 003d56e0
00000000000000000000000000000000 A 00 1
[ 6] .gopclntab PROGBITS 00000000007d56e0 003d56e0
00000000000d4858 0000000000000000 A 00 32
[ 7] .rela RELA 00000000008a9f40 004a9f40
00000000000000180000000000000018 A 140 8
[ 8] .gnu.version VERSYM 00000000008a9f60 004a9f60
00000000000000380000000000000002 A 140 2
[ 9] .gnu.version_r VERNEED 00000000008a9fa0 004a9fa0
00000000000000400000000000000000 A 122 8
[10] .hash HASH 00000000008a9fe0 004a9fe0
00000000000000900000000000000004 A 140 8
[11] .shstrtab STRTAB 0000000000000000 004aa080
0000000000000185000000000000000000 1
[12] .dynstr STRTAB 00000000008aa220 004aa220
00000000000001890000000000000000 A 00 1
[13] .rela.plt RELA 00000000008aa3c0 004aa3c0
00000000000002100000000000000018 A 142 8
[14] .dynsym DYNSYM 00000000008aa5e0 004aa5e0
00000000000002a0 0000000000000018 A 120 8
[15] .got PROGBITS 00000000008ab000 004ab000
00000000000000080000000000000008 WA 00 8
[16] .got.plt PROGBITS 00000000008ab020 004ab020
00000000000000c8 0000000000000008 WA 00 8
[17] .dynamic DYNAMIC 00000000008ab100 004ab100
00000000000001300000000000000010 WA 120 8
[18] .noptrdata PROGBITS 00000000008ab240 004ab240
0000000000016b0c 0000000000000000 WA 00 32
[19] .data PROGBITS 00000000008c1d60 004c1d60
00000000000071100000000000000000 WA 00 32
[20] .bss NOBITS 00000000008c8e80 004c8e80
0000000000008ee0 0000000000000000 WA 00 32
[21] .noptrbss NOBITS 00000000008d1d60 004d1d60
00000000000135c8 0000000000000000 WA 00 32
[22] .interp PROGBITS 0000000000400be4 00000be4
000000000000001c 0000000000000000 A 00 1
[23] .symtab SYMTAB 0000000000000000 004c9000
0000000000030b10 000000000000001824197 8
[24] .strtab STRTAB 0000000000000000 004f9b10
0000000000035eab 000000000000000000 1
[25] .tbss NOBITS 0000000000000000 00000000
00000000000000100000000000000000 WAT 00 8
[26] .debug_abbrev PROGBITS 0000000000000000 0052f9bb
00000000000000fd 000000000000000000 1
[27] .debug_line PROGBITS 0000000000000000 0052fab8
000000000002f2d9 000000000000000000 1
[28] .debug_frame PROGBITS 0000000000000000 0055ed91
000000000003952c 000000000000000000 1
[29] .debug_info PROGBITS 0000000000000000 005982bd
00000000000e6988 000000000000000000 1
[30] .debug_pubnames PROGBITS 0000000000000000 0067ec45
000000000003d502 000000000000000000 1
[31] .debug_pubtypes PROGBITS 0000000000000000 006bc147
0000000000017b10 000000000000000000 1
[32] .debug_aranges PROGBITS 0000000000000000 006d3c57
0000000000000030000000000000000000 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
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
0000000000000000000000000000000000 0
[ 1] .text PROGBITS 0000000000400c00 00000c00
0000000000234d3f 0000000000000000 AX 00 16
[ 2] .plt PROGBITS 0000000000635940 00235940
00000000000001700000000000000010 AX 00 4
[ 3] .rodata PROGBITS 0000000000636000 00236000
000000000019e4c8 0000000000000000 A 00 32
[ 4] .typelink PROGBITS 00000000007d44c8 003d44c8
00000000000012180000000000000000 A 00 8
[ 5] .gosymtab PROGBITS 00000000007d56e0 003d56e0
00000000000000000000000000000000 A 00 1
[ 6] .gopclntab PROGBITS 00000000007d56e0 003d56e0
00000000000d4858 0000000000000000 A 00 32
[ 7] .rela RELA 00000000008a9f40 004a9f40
00000000000000180000000000000018 A 140 8
[ 8] .gnu.version VERSYM 00000000008a9f60 004a9f60
00000000000000380000000000000002 A 140 2
[ 9] .gnu.version_r VERNEED 00000000008a9fa0 004a9fa0
00000000000000400000000000000000 A 122 8
[10] .hash HASH 00000000008a9fe0 004a9fe0
00000000000000900000000000000004 A 140 8
[11] .shstrtab STRTAB 0000000000000000 004aa080
00000000000000dd 000000000000000000 1
[12] .dynstr STRTAB 00000000008aa160 004aa160
00000000000001890000000000000000 A 00 1
[13] .rela.plt RELA 00000000008aa300 004aa300
00000000000002100000000000000018 A 142 8
[14] .dynsym DYNSYM 00000000008aa520 004aa520
00000000000002a0 0000000000000018 A 120 8
[15] .got PROGBITS 00000000008ab000 004ab000
00000000000000080000000000000008 WA 00 8
[16] .got.plt PROGBITS 00000000008ab020 004ab020
00000000000000c8 0000000000000008 WA 00 8
[17] .dynamic DYNAMIC 00000000008ab100 004ab100
00000000000001300000000000000010 WA 120 8
[18] .noptrdata PROGBITS 00000000008ab240 004ab240
0000000000016b0c 0000000000000000 WA 00 32
[19] .data PROGBITS 00000000008c1d60 004c1d60
00000000000071100000000000000000 WA 00 32
[20] .bss NOBITS 00000000008c8e80 004c8e80
0000000000008ee0 0000000000000000 WA 00 32
[21] .noptrbss NOBITS 00000000008d1d60 004d1d60
00000000000135c8 0000000000000000 WA 00 32
[22] .interp PROGBITS 0000000000400be4 00000be4
000000000000001c 0000000000000000 A 00 1
[23] .symtab SYMTAB 0000000000000000 004c9000
0000000000030b10 000000000000001824197 8
[24] .strtab STRTAB 0000000000000000 004f9b10
0000000000035eab 000000000000000000 1
[25] .tbss NOBITS 0000000000000000 00000000
00000000000000100000000000000000 WAT 00 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'
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
0000000000000000000000000000000000 0
[ 1] .text PROGBITS 0000000000400c00 00000c00
0000000000234d3f 0000000000000000 AX 00 16
[ 2] .plt PROGBITS 0000000000635940 00235940
00000000000001700000000000000010 AX 00 4
[ 3] .rodata PROGBITS 0000000000636000 00236000
000000000019e4c8 0000000000000000 A 00 32
[ 4] .typelink PROGBITS 00000000007d44c8 003d44c8
00000000000012180000000000000000 A 00 8
[ 5] .gosymtab PROGBITS 00000000007d56e0 003d56e0
00000000000000000000000000000000 A 00 1
[ 6] .gopclntab PROGBITS 00000000007d56e0 003d56e0
00000000000d4858 0000000000000000 A 00 32
[ 7] .rela RELA 00000000008a9f40 004a9f40
00000000000000180000000000000018 A 140 8
[ 8] .gnu.version VERSYM 00000000008a9f60 004a9f60
00000000000000380000000000000002 A 140 2
[ 9] .gnu.version_r VERNEED 00000000008a9fa0 004a9fa0
00000000000000400000000000000000 A 122 8
[10] .hash HASH 00000000008a9fe0 004a9fe0
00000000000000900000000000000004 A 140 8
[11] .shstrtab STRTAB 0000000000000000 004aa080
00000000000000cd 000000000000000000 1
[12] .dynstr STRTAB 00000000008aa160 004aa160
00000000000001890000000000000000 A 00 1
[13] .rela.plt RELA 00000000008aa300 004aa300
00000000000002100000000000000018 A 142 8
[14] .dynsym DYNSYM 00000000008aa520 004aa520
00000000000002a0 0000000000000018 A 120 8
[15] .got PROGBITS 00000000008ab000 004ab000
00000000000000080000000000000008 WA 00 8
[16] .got.plt PROGBITS 00000000008ab020 004ab020
00000000000000c8 0000000000000008 WA 00 8
[17] .dynamic DYNAMIC 00000000008ab100 004ab100
00000000000001300000000000000010 WA 120 8
[18] .noptrdata PROGBITS 00000000008ab240 004ab240
0000000000016b0c 0000000000000000 WA 00 32
[19] .data PROGBITS 00000000008c1d60 004c1d60
00000000000071100000000000000000 WA 00 32
[20] .bss NOBITS 00000000008c8e80 004c8e80
0000000000008ee0 0000000000000000 WA 00 32
[21] .noptrbss NOBITS 00000000008d1d60 004d1d60
00000000000135c8 0000000000000000 WA 00 32
[22] .interp PROGBITS 0000000000400be4 00000be4
000000000000001c 0000000000000000 A 00 1
[23] .tbss NOBITS 0000000000000000 00000000
00000000000000100000000000000000 WAT 00 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
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
0000000000000000000000000000000000 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 00 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
00000000000000200000000000000000 A 00 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
00000000000000240000000000000000 A 00 4
[ 4] .dynsym DYNSYM 0000000000400298 00000298
00000000000064080000000000000018 A 51 8
[ 5] .dynstr STRTAB 00000000004066a0 000066a0
00000000000131880000000000000000 A 00 1
[ 6] .gnu.hash GNU_HASH 0000000000419828 00019828
000000000000197c 0000000000000000 A 40 8
[ 7] .gnu.version VERSYM 000000000041b1a4 0001b1a4
00000000000008560000000000000002 A 40 2
[ 8] .gnu.version_r VERNEED 000000000041b9fc 0001b9fc
00000000000000900000000000000000 A 54 4
[ 9] .rela.dyn RELA 000000000041ba90 0001ba90
00000000000004680000000000000018 A 40 8
[10] .rela.plt RELA 000000000041bef8 0001bef8
00000000000015c0 0000000000000018 AI 412 8
[11] .init PROGBITS 000000000041d4b8 0001d4b8
000000000000001a 0000000000000000 AX 00 4
[12] .plt PROGBITS 000000000041d4e0 0001d4e0
0000000000000e90 0000000000000010 AX 00 16
[13] .text PROGBITS 000000000041e370 0001e370
00000000000171220000000000000000 AX 00 16
[14] .fini PROGBITS 0000000000435494 00035494
00000000000000090000000000000000 AX 00 4
[15] .rodata PROGBITS 00000000004354a0 000354a0
000000000001bde7 0000000000000000 A 00 32
[16] .gcc_except_table PROGBITS 0000000000451288 00051288
00000000000001710000000000000000 A 00 4
[17] .eh_frame PROGBITS 0000000000451400 00051400
00000000000024040000000000000000 A 00 8
[18] .eh_frame_hdr PROGBITS 0000000000453804 00053804
00000000000006e4 0000000000000000 A 00 4
[19] .tbss NOBITS 0000000000454000 00054000
00000000000000c8 0000000000000000 WAT 00 8
[20] .dynamic DYNAMIC 0000000000454000 00054000
00000000000002200000000000000010 WA 50 8
[21] .got PROGBITS 0000000000454220 00054220
00000000000000100000000000000000 WA 00 8
[22] .got.plt PROGBITS 0000000000454230 00054230
00000000000007580000000000000000 WA 00 8
[23] .data PROGBITS 00000000004549a0 000549a0
0000000000005a20 0000000000000000 WA 00 32
[24] .jcr PROGBITS 000000000045a3c0 0005a3c0
00000000000000080000000000000000 WA 00 8
[25] .tm_clone_table PROGBITS 000000000045a3c8 0005a3c8
00000000000000000000000000000000 WA 00 8
[26] .fini_array FINI_ARRAY 000000000045a3c8 0005a3c8
00000000000000080000000000000000 WA 00 8
[27] .init_array INIT_ARRAY 000000000045a3d0 0005a3d0
00000000000000180000000000000000 WA 00 8
[28] .bss NOBITS 000000000045a400 0005a400
00000000000009d0 0000000000000000 WA 00 32
[29] .comment PROGBITS 0000000000000000 0005a3e8
00000000000000800000000000000001 MS 00 1
[30] .go_export PROGBITS 0000000000000000 0005a468
0000000000016635000000000000000000 1
[31] .note.gnu.gold-ve NOTE 0000000000000000 00070aa0
000000000000001c 000000000000000000 4
[32] .shstrtab STRTAB 0000000000000000 00070abc
0000000000000142000000000000000000 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).
1234567
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.
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
1234567
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.