« gdbの使い方 | メイン | テスト担当者のモチベーション »

LD_PRELOADを使って任意の関数呼び出しにフックしてみる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

尾藤正人(a.k.a BTO)です

先日の社内勉強会のLTでLD_PRELOADについて簡単にやってみました。

LD_PRELOADって?

環境変数$LD_PRELOADを使うと他のライブラリの読み込みの前に任意のライブラリを先に読み込ませることができます。
実行プログラムの形式にELF形式を採用しているOSで使うことができます。
Linuxであれば問題なく使用できるはずです。

何ができるのか

プログラムを変更することなく、任意の関数を上書きしたり、任意の関数にフックすることができます。

libhookwriteを作ってみた

簡単なサンプルプログラムとしてlibhookwriteというのを作ってみました。
libhookwriteはその名の通りwrite(2)にフックをかけることができます。
といってもできることは限られていてファイルのタイムスタンプの更新か、任意のプログラムをsh経由で実行することしかできません。

ダウンロード

libhookwrite-0.0.1.tar.gz

インストール

お約束通り

tar zxvf libhookwrite-0.0.1.tar.gz
cd libhookwrite-0.0.1
./configure
make
sudo make install

rpmでもいけます。

rpmbuild -ta libhookwrite-0.0.1.tar.gz
sudo rpm -ivh libhookwrite-0.0.1-1.i386.rpm

使い方

環境変数$LD_PRELOADにlibhookwrite.soを指定します。
さらに環境変数$HOOKWRITE_UPDATE_FILEか$HOOKWRITE_COMMANDを指定します。

HOOKWRITE_UPDATE_FILEにファイル名を指定すると、write(2)が呼ばれたときに指定されたファイルのタイムスタンプを現在時刻に更新します。

HOOKWRITE_COMMANDに任意のコマンドを指定すると、write(2)が呼ばれたときに指定されたコマンドを/bin/sh経由で実行します。

例えば、次のようなシェルスクリプトcat.shを実行すると、write(2)が呼ばれた時点で$HOME/tmp/catというファイルのタイムスタンプを現在の時刻に更新して、"hoge"と出力します。

#!/bin/sh
HOOKWRITE_UPDATE_FILE=$HOME/tmp/cat ¥
  HOOKWRITE_COMMAND='echo hoge' ¥
  LD_PRELOAD=/usr/lib/libhookwrite.so ¥
  /bin/cat $@

使い道

例えばエディタがファイルを保存したときに、何らかのアクションを起こしたい場合に使えるかもしれません。
プログラムに依存しないので、vimだろうがemacsだろうが何でもいけるはず。
ただ、スワップファイルの書き出しにも反応してしまいますが。(^^;

vimを使ってsymfonyプロジェクトの開発をやってて、ファイルを保存したい瞬間にsymfony ccを走らせてキャッシュクリアしたい場合はこんな感じのaliasを作ればいいでしょうか。

alias vim="LD_PRELOAD=/usr/lib/libhookwrite.so HOOKWRITE_COMMAND='/symfony/project/symfony cc' /usr/bin/vim"

プログラム

プログラム自体は非常に簡単です。

static ssize_t (*write_org) (int fd, const void *buf, size_t count) = NULL;

write(2)と同じプロトタイプ宣言を持つ関数ポインタwrite_orgを定義しています。
オリジナルのwriteを保存するのに使います。

__attribute__((constructor))
void
_save_original_functions()
{
    write_org = (ssize_t(*)(int, const void *, size_t)) dlsym(RTLD_NEXT, "write");
}

dlsym(3)を使ってwriteのアドレスを取り出して、write_orgに保存しています。
__attribute__((constructor))というのはgccの拡張になってて、これを指定しておくとmain関数が呼ばれる前に実行してくれます。

後は単純にwrite関数を改めて定義して、その中に任意の処理を書いておきます。
最後に return write_org(fd, buf, count);でオリジナルの処理を呼び出して終わりです。

参考

man ld.soでマニュアルが読めます。
LD_PRELOADの他にもいろいろな機能がありますので、一度読んでおくといいかもしれません。

fakechroot

LD_PRELOADを使った面白いソフトウェアでfakechrootというのがあります。
chroot(2)の実行にはroot権限が必要ですが、fakechrootでは擬似的に一般ユーザ権限でchrootを実現しています。
LD_PRELOADを使って、ファイル操作に関わる部分にすべてフックをかけて擬似的にchroot環境を提供しているんでしょうね。

Mac OS Xだと

実行プログラムの形式がELFではなくMach-Oが採用されています。
なので、LD_PRELOADは使えないはず。
代わりにDYLD_INSERT_LIBRARIESというのが使えます。

man dyld

詳しく検証してないので、詳細はよくわかってません。

まとめ

LD_PRELOADを使うとプログラムに直接手を加えることなく、外から挙動をコントロールすることができます。
応用範囲は無限大です。

トラックバック

このエントリーのトラックバックURL:
http://www.unoh.net/mt32/mt-tb.cgi/1077

この一覧は、次のエントリーを参照しています: LD_PRELOADを使って任意の関数呼び出しにフックしてみる:

» linux のシステムコールをフックする from DSAS開発者の部屋
最近、とあるクローズドソースなデバイス管理ツールの挙動が気になり、その動作について解析してみることにしました。 プログラムをデバッグしたり解析したい時、... [詳しくはこちら]

» LD_PRELOAD from B-Log
MovableTypeのデータベースをBerkeley DBからSQLiteに変... [詳しくはこちら]

コメント

前にも投稿したんですが、全く関係無い話なんですが、プラッシュで最近BJ大会時に単独で点数を稼ぐ人が増えているようです;

提案として最低2人以上でないとポイントが入らないようにしてください。

ご検討だけでもよろしくお願いします。

あと時々 プラッシュ、掲示板に顔を出してくれると嬉しいです。
未だに沢山のバグやエラーがあるので忙しいとは思いますがよろしくお願いします。いろいろと長くなってしまいましたが反応してくれれば嬉しいです

ありがとうございます!!
可能な限り対応させていただきます

こちらこそありがとうございます!

すいません。63番と786番と872番のユーザーのマイページが消滅しました。btoさんご検討お願いしますm(_ _)m

コメントを投稿


画像の中に見える文字を入力してください。

SaaS提供の高性能CMS RCMS
SaaS提供の高性能CMS
ウノウラボはウノウ株式会社のエンジニア/デザイナーによる大小のアウトプットを行っていく場です。

現在ウノウは絶賛人材募集中です。詳細は求人ページへ。

About

2008年4月 7日 12:12に投稿されたエントリーのページです。

ひとつ前の投稿は「gdbの使い方」です。

次の投稿は「テスト担当者のモチベーション」です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

ウノウサービス