├── .gitignore ├── elf.s ├── elf32.s ├── hello.nim ├── hello2.nim ├── hello3.nim ├── hello4.nim ├── hello5.nim ├── license.txt ├── panicoverride.nim ├── readme.md ├── run.sh ├── run32.sh ├── script.ld └── script32.ld /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !*.* 4 | nimcache/ 5 | *.swp 6 | *.c 7 | *.bin 8 | *.o 9 | *.a 10 | -------------------------------------------------------------------------------- /elf.s: -------------------------------------------------------------------------------- 1 | ; Custom ELF header for the binary. 2 | ; Taken from https://github.com/kmcallister/tiny-rust-demo 3 | ; Inspired by http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html 4 | 5 | bits 64 6 | org 0x00400000 7 | 8 | ehdr: 9 | db 0x7f, "ELF" ; magic 10 | db 2, 1, 1, 0 ; 64-bits, little endian, version 1 11 | 12 | ; This padding is a perfect place to put a string constant! 13 | db "Hello!", 0x0A, 0 14 | 15 | dw 2 ; e_type = executable 16 | dw 0x3e ; e_machine = x86-64 17 | dd 1 ; e_version 18 | dq entry ; e_entry 19 | dq phdr - $$ ; e_phoff 20 | dq 0 ; e_shoff 21 | dd 0 ; e_flags 22 | dw ehdrsize ; e_ehsize 23 | dw phdrsize ; e_phentsize 24 | 25 | ehdrsize equ $ - ehdr 26 | 27 | phdr: 28 | dd 1 ; p_type = loadable program segment & e_phnum,e_sh* 29 | dd 7 ; p_flags = rwx 30 | dq 0 ; p_offset 31 | dq $$, $$ ; p_vaddr, p_paddr 32 | dq filesize ; p_filesz 33 | dq filesize ; p_memsz 34 | dq 0x1000 ; p_align 35 | 36 | phdrsize equ $ - phdr 37 | 38 | incbin "payload.bin" 39 | 40 | filesize equ $ - ehdr 41 | 42 | ; vim: ft=tasm 43 | -------------------------------------------------------------------------------- /elf32.s: -------------------------------------------------------------------------------- 1 | ; Adapted from http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html 2 | 3 | bits 32 4 | org 0x00400000 5 | 6 | ehdr: 7 | db 0x7F, "ELF" ; magic 8 | db 1, 1, 1, 0 ; 32-bits, little endian, version 1 9 | 10 | ; This padding is a perfect place to put a string constant! 11 | db "Hello!", 0x0A, 0 12 | 13 | dw 2 ; e_type 14 | dw 3 ; e_machine = x86 15 | dd 1 ; e_version 16 | dd entry ; e_entry 17 | dd phdr - $$ ; e_phoff 18 | dd 0 ; e_shoff 19 | dd 0 ; e_flags 20 | dw ehdrsize ; e_ehsize 21 | dw phdrsize ; e_phentsize 22 | dw 1 ; e_phnum 23 | dw 0, 0, 0 ; e_sh* 24 | 25 | ehdrsize equ $ - ehdr 26 | 27 | phdr: 28 | dd 1 ; p_type 29 | dd 0 ; p_offset 30 | dd $$, $$ ; p_vaddr, p_paddr 31 | dd filesize ; p_filesz 32 | dd filesize ; p_memsz 33 | dd 5 ; p_flags 34 | dd 0x1000 ; p_align 35 | 36 | phdrsize equ $ - phdr 37 | 38 | incbin "payload.bin" 39 | 40 | filesize equ $ - ehdr 41 | 42 | ; vim: ft=tasm 43 | -------------------------------------------------------------------------------- /hello.nim: -------------------------------------------------------------------------------- 1 | echo "Hello!" 2 | -------------------------------------------------------------------------------- /hello2.nim: -------------------------------------------------------------------------------- 1 | import syscall 2 | 3 | proc main {.exportc: "_start".} = 4 | discard syscall(EXIT, 0) 5 | -------------------------------------------------------------------------------- /hello3.nim: -------------------------------------------------------------------------------- 1 | import syscall 2 | 3 | const STDOUT = 1 4 | 5 | proc write(fd: cint, buf: cstring, len: csize): clong {.inline, discardable.} = 6 | syscall(WRITE, fd, buf, len) 7 | 8 | proc exit(n: clong): clong {.inline, discardable.} = 9 | syscall(EXIT, n) 10 | 11 | proc main {.exportc: "_start".} = 12 | write STDOUT, "Hello!\n", 7 13 | exit 0 14 | -------------------------------------------------------------------------------- /hello4.nim: -------------------------------------------------------------------------------- 1 | import syscall 2 | 3 | const STDOUT = 1 4 | 5 | proc write(fd: cint, buf: cstring, len: csize): clong {.inline, discardable.} = 6 | syscall(WRITE, fd, buf, len) 7 | 8 | proc exit(n: clong): clong {.inline, discardable.} = 9 | syscall(EXIT, n) 10 | 11 | proc main {.exportc: "_start".} = 12 | write STDOUT, cast[cstring](0x00400008), 7 13 | exit 0 14 | -------------------------------------------------------------------------------- /hello5.nim: -------------------------------------------------------------------------------- 1 | import syscall 2 | 3 | const STDOUT = 1 4 | 5 | proc write(fd: cint, buf: cstring, len: csize): clong {.inline, discardable.} = 6 | syscall(WRITE, fd, buf, len) 7 | 8 | proc exit(n: clong) {.inline, noReturn.} = 9 | discard syscall(EXIT, n) 10 | 11 | proc main {.exportc: "_start".} = 12 | write STDOUT, cast[cstring](0x00400008), 7 13 | exit 0 14 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Dennis Felsing 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /panicoverride.nim: -------------------------------------------------------------------------------- 1 | proc rawoutput(s: string) = discard 2 | proc panic(s: string) = discard 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Nim binary size from 160 KB to 150 Bytes 2 | 3 | ## [Accompanying blog article](http://hookrace.net/blog/nim-binary-size/) 4 | 5 | Uses the [nim-syscall](https://github.com/def-/nim-syscall) library. 6 | 7 | ## x86-64 8 | 9 | Results from the article, on Linux x86-64 with GCC 5.1 and Clang 3.6.0: 10 | 11 | $ nimble install syscall 12 | 13 | $ ./run.sh 14 | == Using the C Standard Library == 15 | hello_unoptimized 163827 16 | hello_release 62131 17 | hello_optsize 25248 18 | hello_optsize_strip 18552 19 | hello_gcnone 10344 20 | hello_standalone 6208 21 | 22 | == Disregarding the C Standard Library == 23 | hello2 1776 24 | hello3 952 25 | 26 | == Custom Linking == 27 | hello3_custom 158 28 | hello4_custom 150 29 | hello5_custom 149 30 | 31 | $ objdump -rd nimcache/hello5.o 32 | ... 33 | 0000000000000000 <_start>: 34 | 0: b8 01 00 00 00 mov $0x1,%eax 35 | 5: ba 07 00 00 00 mov $0x7,%edx 36 | a: be 08 00 40 00 mov $0x400008,%esi 37 | f: 48 89 c7 mov %rax,%rdi 38 | 12: 0f 05 syscall 39 | 14: 31 ff xor %edi,%edi 40 | 16: b8 3c 00 00 00 mov $0x3c,%eax 41 | 1b: 0f 05 syscall 42 | ... 43 | 44 | $ ./run.sh --cc:clang 45 | == Using the C Standard Library == 46 | hello_unoptimized 171989 47 | hello_release 33435 48 | hello_optsize 29339 49 | hello_optsize_strip 22704 50 | hello_gcnone 10400 51 | hello_standalone 6248 52 | 53 | == Disregarding the C Standard Library == 54 | hello2 1840 55 | hello3 952 56 | 57 | == Custom Linking == 58 | hello3_custom 160 59 | hello4_custom 152 60 | hello5_custom 151 61 | 62 | ## x86 63 | 64 | Works on 32bit x86 now, also with GCC 5.1 and Clang 3.6.0: 65 | 66 | $ ./run32.sh 67 | == Using the C Standard Library == 68 | hello_unoptimized 147301 69 | hello_release 60001 70 | hello_optsize 23127 71 | hello_optsize_strip 17836 72 | hello_gcnone 9636 73 | hello_standalone 5520 74 | 75 | == Disregarding the C Standard Library == 76 | hello2 1488 77 | hello3 696 78 | 79 | == Custom Linking == 80 | hello3_custom 127 81 | hello4_custom 119 82 | hello5_custom 116 83 | 84 | $ ./run32.sh --cc:clang 85 | == Using the C Standard Library == 86 | hello_unoptimized 143221 87 | hello_release 27261 88 | hello_optsize 23165 89 | hello_optsize_strip 17888 90 | hello_gcnone 9688 91 | hello_standalone 5564 92 | 93 | == Disregarding the C Standard Library == 94 | hello2 832 95 | hello3 484 96 | 97 | == Custom Linking == 98 | hello3_custom 126 99 | hello4_custom 118 100 | hello5_custom 116 101 | 102 | $ objdump -rd nimcache/hello5.o 103 | ... 104 | 00000000 <_start>: 105 | 0: 53 push %ebx 106 | 1: b8 04 00 00 00 mov $0x4,%eax 107 | 6: bb 01 00 00 00 mov $0x1,%ebx 108 | b: b9 08 00 40 00 mov $0x400008,%ecx 109 | 10: ba 07 00 00 00 mov $0x7,%edx 110 | 15: cd 80 int $0x80 111 | 17: b8 01 00 00 00 mov $0x1,%eax 112 | 1c: 31 db xor %ebx,%ebx 113 | 1e: cd 80 int $0x80 114 | ... 115 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NIM="nim --verbosity:0 --hints:off "$@"" 4 | 5 | output() { 6 | printf "%-20s %6s\n" $1 $(wc -c < $1) 7 | #./$1 8 | } 9 | 10 | echo "== Using the C Standard Library ==" 11 | 12 | # hello1.nim 13 | $NIM -o:hello_unoptimized c hello 14 | output hello_unoptimized 15 | 16 | $NIM -d:release -o:hello_release c hello 17 | output hello_release 18 | 19 | $NIM -d:release --opt:size -o:hello_optsize c hello 20 | output hello_optsize 21 | 22 | $NIM -d:release --opt:size -o:hello_optsize_strip c hello 23 | strip -s hello_optsize_strip 24 | output hello_optsize_strip 25 | 26 | $NIM --gc:none -d:release --opt:size -o:hello_gcnone c hello 27 | strip -s hello_gcnone 28 | output hello_gcnone 29 | 30 | $NIM --os:standalone -d:release -o:hello_standalone c hello 31 | strip -s hello_standalone 32 | output hello_standalone 33 | 34 | echo 35 | echo "== Disregarding the C Standard Library ==" 36 | 37 | # hello2.nim 38 | $NIM --os:standalone -d:release --passL:-nostdlib c hello2 39 | strip -s hello2 40 | output hello2 41 | 42 | # hello3.nim 43 | $NIM --os:standalone -d:release --passL:-nostdlib --noMain --passC:-ffunction-sections --passC:-fdata-sections --passL:-Wl,--gc-sections c hello3 44 | strip -s hello3 45 | output hello3 46 | 47 | echo 48 | echo "== Custom Linking ==" 49 | 50 | custom_elf() { 51 | $NIM --app:staticlib --os:standalone -d:release --noMain --passC:-ffunction-sections --passC:-fdata-sections --passL:-Wl,--gc-sections c $1 52 | ld --gc-sections -e _start -T script.ld -o payload nimcache/$1.o 53 | objcopy -j combined -O binary payload payload.bin 54 | ENTRY=$(nm -f posix payload | grep '^_start ' | awk '{print $3}') 55 | nasm -f bin -o $1_custom -D entry=0x$ENTRY elf.s 56 | chmod +x $1_custom 57 | output $1_custom 58 | } 59 | 60 | custom_elf hello3 61 | 62 | # hello4.nim 63 | custom_elf hello4 64 | 65 | # hello5.nim 66 | custom_elf hello5 67 | -------------------------------------------------------------------------------- /run32.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NIM="nim --verbosity:0 --hints:off "$@" --cpu:i386 --passC:-m32 --passL:-m32" 4 | 5 | output() { 6 | printf "%-20s %6s\n" $1 $(wc -c < $1) 7 | #./$1 8 | } 9 | 10 | echo "== Using the C Standard Library ==" 11 | 12 | # hello1.nim 13 | $NIM -o:hello_unoptimized c hello 14 | output hello_unoptimized 15 | 16 | $NIM -d:release -o:hello_release c hello 17 | output hello_release 18 | 19 | $NIM -d:release --opt:size -o:hello_optsize c hello 20 | output hello_optsize 21 | 22 | $NIM -d:release --opt:size -o:hello_optsize_strip c hello 23 | strip -s hello_optsize_strip 24 | output hello_optsize_strip 25 | 26 | $NIM --gc:none -d:release --opt:size -o:hello_gcnone c hello 27 | strip -s hello_gcnone 28 | output hello_gcnone 29 | 30 | $NIM --os:standalone -d:release -o:hello_standalone c hello 31 | strip -s hello_standalone 32 | output hello_standalone 33 | 34 | echo 35 | echo "== Disregarding the C Standard Library ==" 36 | 37 | # hello2.nim 38 | $NIM --os:standalone -d:release --passL:-nostdlib c hello2 39 | strip -s hello2 40 | output hello2 41 | 42 | # hello3.nim 43 | $NIM --os:standalone -d:release --passL:-nostdlib --noMain --passC:-ffunction-sections --passC:-fdata-sections --passL:-Wl,--gc-sections c hello3 44 | strip -s hello3 45 | output hello3 46 | 47 | echo 48 | echo "== Custom Linking ==" 49 | 50 | custom_elf() { 51 | $NIM --app:staticlib --os:standalone -d:release --noMain --passC:-ffunction-sections --passC:-fdata-sections --passL:-Wl,--gc-sections c $1 52 | ld -m elf_i386 --gc-sections -e _start -T script32.ld -o payload nimcache/$1.o 53 | objcopy -j combined -O binary payload payload.bin 54 | ENTRY=$(nm -f posix payload | grep '^_start ' | awk '{print $3}') 55 | nasm -f bin -o $1_custom -D entry=0x$ENTRY elf32.s 56 | chmod +x $1_custom 57 | output $1_custom 58 | } 59 | 60 | custom_elf hello3 61 | 62 | # hello4.nim 63 | custom_elf hello4 64 | 65 | # hello5.nim 66 | custom_elf hello5 67 | -------------------------------------------------------------------------------- /script.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | . = 0x400070; 3 | 4 | combined . : AT(0x400070) ALIGN(1) SUBALIGN(1) { 5 | *(.text*) 6 | *(.data*) 7 | *(.rodata*) 8 | *(.bss*) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /script32.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | . = 0x400054; 3 | 4 | combined . : AT(0x400054) ALIGN(1) SUBALIGN(1) { 5 | *(.text*) 6 | *(.data*) 7 | *(.rodata*) 8 | *(.bss*) 9 | } 10 | } 11 | --------------------------------------------------------------------------------