"main関数の無いプログラム"の解析

id:selvaggio:20061007:1160220826のやっていたmain関数のないプログラムの解析。
まずは、コードそのものを示しておく。

#include "stdio.h"

void __stdcall put(char c)
{
	putchar(c);
}
extern int ret[1];

unsigned int main[] = {
	 'Ah', 'HHH�\0', 'hHHH', (int)ret, 'hHHH', (int)put, -61,
};
int ret[] = {-61};

まったくもってすばらしい。main関数がないこと自体は別にそれほど驚くことじゃないんだけど、その中身のほとんどがASCIIの範囲に収まっていることがすばらしい。
さて詳しく解説しようか。

main関数がなくてもいい理由

main配列がmain関数の代わりをしているから。
この辺りを詳しく書くと本が一冊できるので、非常に簡単に説明するよ。詳しく知りたいなら、GNU開発ツールを勧めとく。
まず、main関数の場合、mainというのは一連の処理(関数の中身)の先頭に付けられた名前になる。
そして、main配列の場合、mainというのは一連のデータ(配列の中身)の先頭につけられた名前になる。
さらに、昔からコンピュータは処理は"機械語の並び"というデータに変換している。
だから、main配列の中身を"機械語の並び"にしておけば、あたかもmain関数であるかのように扱える。

main配列の中身

問題は、main配列の中身。なんで、これがASCIIで書けるのかが肝。
シングルクオートの中に4文字も書いているのは、たぶんunsigned intの配列だからだろう。(sizeof(unsigned int) == 4 * sizeof(char))
まずは、文字を16進数に直す。

文字 16進数
Ah 00 00 41 68
HHH�\0 48 48 48 00
hHHH 68 48 48 48
(int)ret -
hHHH 68 48 48 48
(int)put -
-61 ff ff ff c3

そして、これをmain配列の中身を逆アセンブルしたものと比較する。

0:	68 41 00 00 00       	push   $0x41 ; 'A'のASCIIコード
   5:	48                   	dec    %eax
   6:	48                   	dec    %eax
   7:	48                   	dec    %eax
   8:	48                   	dec    %eax
   9:	48                   	dec    %eax
   a:	48                   	dec    %eax
   b:	68 1c 00 00 00       	push   $0x1c ; retへのポインタ
  10:	48                   	dec    %eax
  11:	48                   	dec    %eax
  12:	48                   	dec    %eax
  13:	68 00 00 00 00       	push   $0x0 ; putへのポインタ
  18:	c3                   	ret    
  19:	ff                   	(bad)  
  1a:	ff                   	(bad)  
  1b:	ff c3                	inc    %ebx

Intelシリーズは、リトルエンディアンだから、数字が下の桁から格納されていることに注意すれば、この2つが同じものなのは分かると思う。
ここから先はお手上げ。なんで、jmpもcalもないのに、関数が呼べるのよ。