libcを使わずシステムコール呼出し

Plan9でHello, world!に書いたプログラムを普通にコンパイルすると,サイズは36204バイト.Plan9に動的リンクは存在しないので,静的リンクした大きさである.で,strip(1)したサイズは21639バイト.

次にprintを使わず,直接write(2)を使って書いてみる.この場合,サイズは3657バイト.stripして1346バイト.ずいぶん小さくなったな.

#include <u.h>
#include <libc.h>

void
main(int argc, char **argv)
{
	write(1, "Hello, world!\n", 14);
	exits(nil);
}

ここからは,Binary Hacks #25の真似で,libcのwrite(2)を使わずに,直接INT命令を実行して,システムコール呼出ししてみる.Hacks #25ではLinuxカーネルインラインアセンブリのマクロを使っているけど,Plan9インラインアセンブリの書き方を知らないので,次のようなアセンブリファイルを用意した.

cpu% cat asm.s
TEXT xwrite(SB), 1, $0
MOVL $20, AX
INT $64
RET

TEXT xexits(SB), 1, $0
MOVL $8, AX
INT $64
RET

呼出し側のCプログラムはこんな感じ.

cpu% cat hello2.c
#include <u.h>
#include <libc.h>

xwrite(int fd, void *buf, long n);
xexits(char *msg);

void
main(int argc, char **argv)
{
	xwrite(1, "Hello, world!\n", 14);
	xexits(nil);
}

コンパイルしてみるが,1303バイトとあまりサイズは変わらない.nmでシンボルを見ると,libcの関数がいろいろ残っている.8lのmanページを眺めていたら,-lオプションを発見.これを使えば,libc.aはリンクされなそうだ.-Emainはmainをエントリポイントにするためのおまじない.デフォルトはlibc.aの_mainがエントリポイントになる.

cpu% 8c hello2.c
cpu% 8a asm.s
cpu% 8l -l -Emain hello2.8 asm.8
cpu% ls -l 8.out
--rwxrwxr-x M 1390850 oraccha oraccha 481 Nov 23 21:25 8.out
cpu% strip 8.out
cpu% ls -l 8.out
--rwxrwxr-x M 1390850 oraccha oraccha 114 Nov 23 21:27 8.out
cpu% 8.out
Hello, world!

ということで,114バイトまで小さくなった.

さて,8.outをヘキサダンプしてみるとわかるけど,Plan9の実行ファイル形式はELFじゃないね.Hacks #25は488バイト,頑張れば58バイトまで小さくなるらしいが,正攻法でここまでになるのは,Plan9 a.outフォーマットがELFより,ずいぶんシンプルだからか.
ファイルの先頭32ビットのマジックナンバはアーキテクチャごとに異なっていて,IA32の場合は0x1ebになるようだ.詳細は"man a.out"を参照.

cpu% file 8.out
hello: 386 plan 9 executable
cpu% xd 8.out
0000000  000001eb 00000042 00000010 00000000
0000010  00000000 00001020 00000000 00000000
0000020  83ec10b8 01000000 890424b8 00200000
0000030  89442404 b80e0000 00894424 08e81000
0000040  0000c704 24000000 00e80c00 000083c4
0000050  10c3b814 000000cd 40c3b808 000000cd
0000060  40c34865 6c6c6f2c 20776f72 6c64210a
0000070  00000000
0000072 

Plan9には,nm(1)やstrings(1)はあるけど,objdumpやreadelf相当のコマンドはないかな.やっぱりacid(1)か.