mainを一度も呼ばないばかりか蹂躙する

shinhさんの「ふとイヤなコードを思いつきました」インスパイヤされてみました。

% cat iyana.c
#include <stdio.h>
#include <stdlib.h>

int main;
__attribute__((constructor, destructor))
static void x() {
  if (main) puts("world!");
  else exit(main = puts("hello"));
}

% gcc -Wall iyana.c
iyana.c:4: warning: ‘main’ is usually a function

% ./a.out
hello
world!

意味はありません。っていうかこの警告ははじめて見たわ。教えてくれなくても存じていましてよ。

(追記) shinhさんの8/29の日記にさらに凄いのが。


トラックバックしてくれているのでそちらをクリック。素敵。特に2番目の、動的に生成したコードが自分自身を参照する様に感動しました。
えーと、1番目の数字でほげほげするのと、3番目のセクション移動を真似してみます。

__attribute((section(".text"))) main = 2425393296;
_() { __attribute((constructor)) _() { puts("hello"); } puts("world!"); }

やっぱり、常識としてmainは.textに置くべきだよなーっと(棒読み)。全ての環境で動くかどうかは知りません。わたしはFC5(x86_64)でgcc -m32。

(追記2)あんまりなコードなので解説します


上記をもうすこし読めるように書き直すと、次のようになります。関数がネストしていることに意味はないので(なるべく少ない行数でなんとかしたかったのでネストしただけ)、外に出しました。なお、このように書き直すと移植性(?)も微妙に向上します。

__attribute((section(".text"))) int main = 0x90909090;
void f1() { puts("world!"); }
__attribute((constructor)) void f2() { puts("hello"); }

0x90はx86の命令でNOPです。なので、0x90909090は NOP 4つと同じです。これを覚えておいて、ソースをコンパイル・リンク・逆アセンブルするとこうなります。

% objdump -d ./a.out | grep -A20 '<main>'
08048384 <main>:
 8048384:       90 90 90 90                                         ....

08048388 <f1>:
 8048388:       55                      push   %ebp
 8048389:       89 e5                   mov    %esp,%ebp
 804838b:       83 ec 08                sub    $0x8,%esp
 804838e:       c7 04 24 64 84 04 08    movl   $0x8048464,(%esp)
 8048395:       e8 0e ff ff ff          call   80482a8 <puts@plt>
 804839a:       c9                      leave
 804839b:       c3                      ret

0804839c <f2>:
 804839c:       55                      push   %ebp
 804839d:       89 e5                   mov    %esp,%ebp
 804839f:       83 ec 08                sub    $0x8,%esp
 80483a2:       c7 04 24 6b 84 04 08    movl   $0x804846b,(%esp)
 80483a9:       e8 fa fe ff ff          call   80482a8 <puts@plt>
 80483ae:       c9                      leave
 80483af:       c3                      ret

f2()がコンストラクタ指定されているのでmain()より前に呼ばれ、"hello" が出ます。次にmain()が普通に呼ばれますが、この関数(?)にはnopしかないので、華麗にfall throughしてf1()に突入します。で、"world!" が出て、f1()の終わりのretがmain()のretとみなされます。変数mainを.textセクションにもってきたのは、ELF上でmain変数をf1関数のすぐ上に位置させるためです。というわけで、コンパイラやリンカの気分次第で全く動作しなくなります。以上。


というかですね、

x86では__attribute__((naked))はサポートされていない、そんなふうに考えていた時期が俺にもありました

ということですよ(いや実際サポートされてないんだけど)。今回、リターンしないmainを書いて (いや main = 195; を見て) 目が覚めました。そうか、全部数字で書けばnakedじゃん、と。一見当然そうなことだけど、こういうのを気づきというんだな(違う)。


どう見ても堕落したCプログラマのレベル-9です。本(ry




(追記) objdump -d じゃなくて objdump -D のほうがよいですね。-Dなら、90 90 90 90 のところもちゃんと逆アセンブルしてくれます。-d だと、(.textの中であっても)何らかの方法でコードとデータを見分けようと頑張ってしまう模様。