hello-minimum - libcを使わずに hello world
目的
BINARY HACKS "HACK #26, glibcを使わないで Hello World を書く" のARM版を作りたい。
できるならサイズはとことん削りたい。
結果
ARM命令版で174バイト、Thumb-2命令版で154バイトの Hello World を作った。
ポイントは以下:
- system call を直接呼出し
- エントリポイントをmain()に変更
- 不要セクションとセクションヘッダを削除
以下、詳細。
Cソースファイル
完全に二番煎じだが、とりあえず ARM で最小版を作りたい。
BINARY HACKS (pp.94-98) には、以下のことが書かれている:
- system call を直接呼び出して、glibc を使わずにプログラミングする
- リンク時にエントリポイントを変更する
- stripする
これを参考に、下記 C ソースを作成:
- hello-minimum.c
- https://sssvn.jp/svn/spikelet/c/arm-minimum/hello-minimum.c
/* * minimum hello-world for ARM * * NOTICE: this program cannot receive command line arguments */ static void write(int fd, const void *buf, unsigned int count); static void exit_group(int code); /* following macros are copied from linux/arch/arm/include/asm/unistd.h */ #define __SYS_REG(name) register long __sysreg __asm__("r7") = __NR_##name; #define __SYS_REG_LIST(regs...) "r" (__sysreg) , ##regs #define __syscall(name) "swi\t0" #define __NR_SYSCALL_BASE 0 #define __NR_write (__NR_SYSCALL_BASE+ 4) #define __NR_exit_group (__NR_SYSCALL_BASE+248) #define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ type name(type1 arg1,type2 arg2,type3 arg3) { \ __SYS_REG(name) \ register long __r0 __asm__("r0") = (long)arg1; \ register long __r1 __asm__("r1") = (long)arg2; \ register long __r2 __asm__("r2") = (long)arg3; \ __asm__ __volatile__ ( \ __syscall(name) \ : \ : __SYS_REG_LIST( "r" (__r0), "r" (__r1), "r" (__r2) ) \ : "memory" ); \ } #define _syscall1(type,name,type1,arg1) \ type name(type1 arg1) { \ __SYS_REG(name) \ register long __r0 __asm__("r0") = (long)arg1; \ __asm__ __volatile__ ( \ __syscall(name) \ : \ : __SYS_REG_LIST( "r" (__r0) ) \ : "memory" ); \ } /* * define system call wrapper */ _syscall3(void,write,int,fd,const void*,buf,unsigned int,count) _syscall1(void,exit_group,int,status) /* * minimum hello-world */ void main(void) { write(1, "Hello, world.\n", 14); exit_group(0); }
syscall呼び出しマクロは、ARMアーキ用の定義を参考に、戻り値を格納する部分を削除した(どうせ戻り値は見ないので)。
これで、以下のサイズになる。
命令セット | サイズ |
---|---|
ARM | 472 |
Thumb-2 | 452 |
意外に大きい。もっと減らせないか?
ロード&実行に不要な部分を削除
この時の hello-minimum のELFセクションヘッダは、以下:
Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00008074 000074 00002c 00 AX 0 0 4 [ 2] .rodata PROGBITS 000080a0 0000a0 00000f 01 AMS 0 0 1 [ 3] .ARM.attributes ARM_ATTRIBUTES 00000000 0000af 000035 00 0 0 1 [ 4] .shstrtab STRTAB 00000000 0000e4 000029 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
これらのうち、alloc されないものは実行時には使わないから不要。
また、.shstrtab セクションのうしろにも、203バイトほどデータ(セクションヘッダ)あり。
セクションヘッダも、ローダは無視するので、不要。
ということで、実行に不要なデータを削るようにする:
dd if=hello-minimum.strip of=hello-minimum bs=1 count=`arm-none-linux-gnueabi-readelf -S hello-minimum.strip | awk '/\.ARM\.attributes/{print strtonum("0x" $6) - 1}'`
これで、以下のサイズになる。
命令セット | サイズ |
---|---|
ARM | 174 |
Thumb-2 | 154 |
セクションヘッダを削除してしまうと、objdump や readelf がエラーになる ("File truncated" や "Unable to read ... of section headers")。
しかし、実行する分には問題ないので、ギリギリROMサイズを削るわざとしては、ありなのかもしれない。