hello-minimum - libcを使わずに hello world

目的

BINARY HACKS "HACK #26, glibcを使わないで Hello World を書く" のARM版を作りたい。
できるならサイズはとことん削りたい。

結果

ARM命令版で174バイト、Thumb-2命令版で154バイトの Hello World を作った。

ポイントは以下:

  1. system call を直接呼出し
  2. エントリポイントをmain()に変更
  3. 不要セクションとセクションヘッダを削除

以下、詳細。

Cソースファイル

完全に二番煎じだが、とりあえず ARM で最小版を作りたい。

BINARY HACKS (pp.94-98) には、以下のことが書かれている:

  1. system call を直接呼び出して、glibc を使わずにプログラミングする
  2. リンク時にエントリポイントを変更する
  3. 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サイズを削るわざとしては、ありなのかもしれない。