b2con

はじめに

これはBinary 2.0 カンファレンスにおいて、浜地が発表したプレゼンを html 化したものです。 実行ファイルはb2con_hamaji.zipとして置いてあります。 Windows の環境があるかたはぜひこちらをご覧下さい。 改造済みSPSのソースはものすごい汚ないので見ると目がほげほげなので目薬を用意下さい。

Dynamic Programming Language C

- 私は誰? -

  • Shinichiro Hamaji
  • shinichiro.hamaji _at_ gmail.com
  • 2005/12/02 Binary 2.0 カンファレンス
  • SPS, SDL Presentation System

私は誰?

shinichiro.h / shinh など

そこらの趣味プログラマ

http://shinh.skr.jp/

好きなもの

→ はどうでもいい

嫌いなもの

似たコードの連続

こういうのとか

suite.addTest(test1);
suite.addTest(test2);
suite.addTest(test3);
suite.addTest(test4);
suite.addTest(test5);

嫌いなもの

こういうのとか

rb_define_method(
  rb_cFile,"atime",rb_file_atime,0);
rb_define_method(
  rb_cFile,"mtime",rb_file_mtime,0);
rb_define_method(
  rb_cFile,"ctime",rb_file_ctime,0);
rb_define_method(
  rb_cFile,"chmod",rb_file_chmod,1);
rb_define_method(
  rb_cFile,"chown",rb_file_chown,2);

Ruby の array.c より

あげくの果てには

こんなんなっちゃう

suite.addTest(test1);
suite.addTest(test2);
suite.addTest(test3);
suite.addTest(test3);
suite.addTest(test5);

すごい困る!

解決法

スクリプトでコード生成

ダサい

生成してから編集すると再生成しにくくてイヤン問題

(行数は増えるからおしごとではいいけど…)

(いいのか!?)

解決法

C/C++ のマクロ

CppUnit? とか

少しマシになっただけ

CPPUNIT_TEST_SUITE(HogeTest);
CPPUNIT_TEST(testHoge1);
CPPUNIT_TEST(testHoge2);
CPPUNIT_TEST(testHoge3);
CPPUNIT_TEST_SUITE_END();

解決法

そうだリフレクションだ

こんな感じができるといいな

void** funcs = read_exe(argv[0]);
int i;
for (i = 0; i < func_size; i++) {
  ((void (*)())funcs[i])();
}

でも C には…

リフレクションありませんからっ

Java を使う

→ 論外

なんとかやってしまおう

この話の本題

Dynamic Programming Language C

道のり

which $0

ldd

nm

c++filt

取得した関数のcall

→ これらを実行時に行う

which $0

私は誰?

自分のファイル名

自分のファイルハンドル

自分のモジュールハンドル

私は誰?

案外わからない

argv[0] を使う?

$PATH 使った場合はダメ

/bin/ls なのに argv[0] == ls

私は誰?

環境依存するしかない

Linux :

readlink("/proc/self/exe")

MacOSX:

_dyld_get_image_name

Win32 :

HMODULE h = LoadLibrary(NULL);
GetModuleFileName(h, buf, 1024);

私は誰?

簡単なことでも意外と面倒

(バッド)ノウハウの山

  • #ifdef の嵐
  • dlinfo(3)がLinuxのmanに無いとか
  • OSX では BFD が…とか
  • WINEが…とか → ライブラリ化したい

詳しいことは省略して話します

道のり

which $0

ldd

nm

c++filt

取得した関数のcall

ldd

ロードされているDLL

フルパス

/lib/libc-2.3.5.so

ロードアドレス

0x41000000

→ 「私は誰?」以上に環境依存

  • /proc/self/maps
  • dl_iterate_phdr

道のり

which $0

ldd

nm

c++filt

取得した関数のcall

nm

オブジェクトファイルを見てシンボルを取ってくる

0805bf84 T bfd_make_readable
0805cc51 T bfd_make_section
0805d0c6 T bfd_make_section_anyway

本来なら一番面倒な部分

→ オブジェクトファイルは千差万別

libbfdが環境依存をほとんど吸収してくれる

libbfd

GNU binutilsのライブラリ

オブジェクトファイルを読み書きできる

ex. シンボル、行情報、etc...

多数の対応フォーマット

ちょっと環境依存な部分も

GPL

道のり

which $0

ldd

nm

c++filt

取得した関数のcall

c++filt

C++のシンボルは読めない

デマングル

? _ZN7DtrFile9load_symsEv

! DtrFile?::load_syms()

GNU libiberty の cp_demangle

→ 使いにくいAPIだが…

c++filt

副産物

関数の引数の型がわかる

でも返り値の型はわからない

  • イマイチ
  • D言語はわかる

クラス名がわかる

  • 特定クラスのメソッド一覧など

道のり

which $0

ldd

nm

c++filt

取得した関数のcall

関数 call

関数のシグネチャが決まっていれば簡単

void* fp = get_func("puts");
((int (*)(char*))fp)("hello");

関数 call

関数のシグネチャが決まっていれば簡単

int i;
for (i = 0; i < func_size; i++) {
  ((void (*)())funcs[i])();
}

関数 call

わかっていない場合

libffi

→ GCJ で利用されている

ffcall

→ GNUStep で利用されている

libffi

void* fp = puts;
void* args = hogehoge;
ffi_type* ats[1];
ats[0] = &ffi_type_pointer;
int ret;
ffi_cif cif;

ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1,
             &ffi_type_sint32, ats);
ffi_call(&cif, FFI_FN(fp), &ret, args);

→ 動的ディスパッチが可能

まとめ

以下のようにリフレクションを実現

EXEとDLLを取得

libbfd で関数アドレスを取得

C++ ならデマングリング

libffiやffcallで関数起動

問題点

JavaやRubyなどと比較

可搬性にやや難

→ 努力すればカバーできる

わかりにくいエラーメッセージ

→ 最悪 SegFault?

言語に統合されていない

構造体メンバが見れない

→ シリアライズができない。今後の課題

いいところ

楽しい!

→ 自己満足でいいじゃないか

→ 開きなおり

活用実例

しー言語

適当に作ったデモ言語

C の関数をバインディング無しで実行

Linux, FreeBSD, NetBSD, OpenBSD, Solaris, MacOSX, Windows で動作

活用実例

しー言語

#!./she
puts "hello world!"
!load  "m"
dx = cos 3.2
printf "%f" dx

言語というよりバッチ

! ではじまるのが予約語

変数の型は先頭の文字で判定

  • d => double, i => int, ...
  • ダメな意味で低レベル

活用実例

Dynamic Test Runner

.o ファイルをロード・再配置

  • 再配置時にassertを乗っとる
  • 勝手に abort させないため

内部の dtr_test を含む関数をかたっぱしから実行

./dtr test/*.o test/*.a -lm -lz

→ おてがる

Dynamic Test Runner

発想

テストに時間かけるのはバカらしい

俺はテストがしたいだけなんだ!

テストフレームワークの勉強がしたいんじゃない!

Dynamic Test Runner

書きたい時にすぐ書く

外部ファイル一切不要

  • dtr本体だけ

dtr_test を含む関数をいきなりどっかに書く

どこからも呼び出されないからリンクされれば消える

Dynamic Test Runner

現状

環境依存

  • Linux, FreeBSD 以外では未確認
  • CPU も x86 だけ

対応言語はCとC++とObjC

  • GCJとDも対応を実装中

Ruby/C++, Ruby/D

Ruby からクラスを直で

SWIGはいいものだが正直めんどい

Objective-C++ みたいに

Java & Groovy みたいに

できるといいな

ちょっと作ってみただけ

Ruby/C++

使用例

C++側

class BankAccount {
public:
    explicit BankAccount(int d);
    int dollars();
    void set_dollars(int d);
    void deposit(int d);
    void withdraw(int d);

private:
    int dollars_;
};

Ruby/C++

使用例

Ruby側

require 'rubycpp'

BankAccount =
  Cplusplus::declare_class(
  "bankaccount", "BankAccount", 4)
b = BankAccount.new(100)
b.deposit(300)
p b.dollars

→ バインディング不要

Ruby/C++

問題点

  • インライン関数呼べない
  • C++のシンボルには返り値情報が無い
  • 親クラスのメソッドを呼んでない
  • オーバーロードを解決していない
  • std::string を扱ってない
  • たぶん Linux のみ

終わり

言いたかったこと

コピペでコード書きたくない!

オブジェクトファイルの情報をもっと有効活用しよう!

なんか楽しいし!

SPS

SDL Presentation System

  • Zinniaさん作
  • FPSカウンタのあるプレゼンツール

SDL?

  • Simple Directmedia Layer
  • クロスプラットフォーム
  • 要はゲームライブラリ

プレゼン資料もバイナリ配布の時代