エクスポートされていない関数のアドレスを調べる (OS X用)

(function とか symbol とかの使い分けがよく分からないので、文中は「関数」で統一しています)

HTSymbolHook というライブラリを github で公開しました。mach_overrideObjective-C ラッパーで、関数ポインタをオーバーライドできるライブラリです。単なるラッパーではなく関数ポインタを探す機能を付けました。

mach_override は関数ポインタを関数ポインタで置き換える機能を持っています。以前は置き換える対象を関数名のC文字列で指定することができていたのですが現行バージョンではなくなっています。つまり関数ポインタを自前で探す必要があります。OS X はバージョンを重ねるごとに関数ポインタを探すことが難しくなり、現状 dlsym() という関数を使うくらいしか簡単な方法はありません。しかし dlsym() はエクスポートされている関数しか探せないという行儀の良い仕様なのです。オーバーライドしたい関数がエクスポートされているとは限りません。

いろいろ調べ回った結果、Mach-O イメージを直接走査すればエクスポートされていない関数も取得できることが分かりました。大雑把に書くと

  • mach_header_64 構造体を取得
  • それを起点に LC_SYMTAB load command から symtab_command 構造体を取得
  • __LINKEDIT セグメントからオフセット情報を取得
  • symtab_command 構造体にある関数情報のオフセットを __LINKEDIT の情報で補正
  • nlist_64 構造体のリストと関数名の文字列リストが手に入る

という手順でした。symtab_command の構造は Mach-Oのシンボルテーブルを読み込む - teru_kusuの日記 にて分かりやすい図解がしてあり参考になりました。

実行コードとしてロードされている Mach-O イメージの場合これだけではまだ不十分で、 _dyld_get_image_vmaddr_slide() という関数を使ってMach-O イメージ全体にかかっているオフセットを補正する必要もありました。あとは nlist_64 構造体のリストを検索して関数名が一致すればめでたく発見です。エクスポートされていない関数も取れます。

というような機能を HTSymbolHook に内蔵しています。

で、これの実行速度はどうなのかというと、dlsym() と比較すると遅いです。300倍くらい遅いです! どうやらエクスポートされている関数は読み込んだ後にソートされて二分探索できるようになっているみたいなのです。速度を気にするような場面じゃないのでこのままでもいいのですが、高速化する手段も用意しました。nlist_64 の順番は固定なので、あらかじめ目的の関数が何番目にあるのか調べておき、そのインデックスから検索開始できるようにもしています(実際にはバージョンアップ対策で何個か前から検索した方が良いと思います)。これは一発で見つかるので dlsym() より速いです。

詳細はソース見てね、ということで https://github.com/hetima/HTSymbolHook