memologue RSSフィード

書いている人

日記というよりは備忘録、ソフトウェア技術者の不定期メモ。あるいはバッドノウハウ集。プライベートで調査した細々した諸々のスナップショット。嘘が散りばめられています。ISO/IEC 14882(C++)とPOSIX, GCC, glibc, ELFの話ばかりで、WindowsやMacの話はありません。特に記載がなければLinux/x86とILP32が前提です。時間の経過と共に古い記事は埋もれてしまいます。検索エンジンから飛んできた場合は、ページ内検索をご利用いただくかgoogleキャッシュを閲覧してみてください。技術的な記事を書きためる場所として使っています。言及してもらえると喜びます。主要な記事の一覧を書いておきます:

にんきコンテンツ: [GCC] mainを一度も呼ばないばかりか蹂躙する / Binary2.0 Conference 2006 発表資料 / C++ で SICP / ついカッとなって実行バイナリにパッチ / hogetrace - 関数コールトレーサ

昔のPOSIX関係の記事: シグナルの送受に依存しない設計にしよう / シグナルハンドラで行ってよい処理を知ろう / マルチスレッドのプログラムでのforkはやめよう / スレッドの「非同期キャンセル」を行わない設計にしよう / スレッドの「遅延キャンセル」も出来る限り避けて通ろう / マルチスレッドプログラミングの「常識」を守ろう / C++でsynchronized methodを書くのは難しい / シグナルハンドラからのforkするのは安全か? / g++ の -fthreadsafe-statics ってオプション知ってます? / 非同期シグナルとanti-pattern / localtimeやstrtokは本当にスレッドセーフにできないのか / UNIXの規格について / マルチスレッドと共有変数 - volatile?なにそれ。 / type punning と strict aliasing / アセンブラで遊ぶ時に便利なgdb設定 / 最近の記事は一覧から

2006-08-28

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

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証