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の中であっても)何らかの方法でコードとデータを見分けようと頑張ってしまう模様。