Android+オレオレ証明書=通信不能
WebViewを使用したAndroidアプリを製作し、信頼できない証明書(いわゆるオレオレ証明書)を使用したWebサイトにアクセスすると、頻繁にリンクをタップしたときに通信不能になってしまった。放っておくと約10分で復活するけど・・・。
頻繁というのはリンクをタップした後、ロードの完了を待たずに別(同じでもいいかも)のリンクをタップするくらいの感じ。
ちなみに WebViewClient の onReceivedSslError() はオーバーライドして、
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { handler.proceed() ; }
な感じで全部もみ消すように実装している。
この現象の詳細を追ってみた。環境はFroyo。
あ、回避策はわかりませんでした。誰か教えてください。(ちゃんとした証明書使えという以外で ^^;)
andoid.net.http, android.webkit 辺りの実装概要
- HTTP通信は http0〜http3 という4本のスレッドで行われる
- スレッドクラスは android.net.http.ConnectionThread
- HTTP通信のリクエストを保持するキューとキューに溜まったリクエストを http0〜3 で処理する実装
- 最後のほうにクラス図(抜粋)載せてます
通信不能時の状態
- 通信不能な状態の時はhttp0〜3全てが、キューへの追加を待つのとは別のところでwaitしている
- キューへの追加待ちの時は android.net.http.ConnectionThread.run() の中でwait
- 通信不能時は android.net.http.HttpsConnection.openConnection() の中でwait
- (id:h13i32maru 氏が発見)
waitしている場所
android.net.http.HttpsConnection.openConnection() の抜粋
01: sslSock = (SSLSocket) getSocketFactory().createSocket(); 02: sslSock.connect(new InetSocketAddress(mHost.getHostName(), mHost.getPort())); 03: 04: SslError error = CertificateChainValidator.getInstance(). 05: doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName()); 06: 07: if (error != null) { // ←オレオレ証明書はエラーとなるのでここはtrue 08: synchronized (mSuspendLock) { 09: mSuspended = true; 10: } 11: boolean canHandle = req.getEventHandler().handleSslErrorRequest(error); 12: synchronized (mSuspendLock) { 13: if (mSuspended) { 14: mSuspendLock.wait(10 * 60 * 1000);
通信不能時のhttpxスレッドはこの14行目のwaitでwaitしている。
waitに至る流れ
mSuspneded を false に変更するのは HttpsConnection.restartConnection() のみ。このwaitから復帰させるための mSuspendLock.notify() を呼ぶのも HttpsConnection.restartConnection() 内のみ。
つまり、waitしぱなっしにならないためには、muSuspened が9行目で true に設定された後、別スレッドでrestartConnection()が呼ばれるか、11行目の req.getEventHandler().handleSslErrorRequest(error) の中で restartConnection() がよばれる必要がある。
restartConnection() を呼ぶのは HttpsConnection.closeConnection() と Request.handleSslErrorResponse(boolean) のみ。
ロードが正常に完了する時のrestartConnection()の呼び出しスタックはこんな感じ。
01: HttpsConnection.restartConnection(boolean) line: 409 02: HttpsConnection(Request).handleSslErrorResponse(boolean) line: 519 03: RequestHandle.handleSslErrorResponse(boolean) line: 119 04: LoadListener.handleSslErrorResponse(boolean) line: 881 05: SslErrorHandler.handleSslErrorResponse(LoadListener, SslError, boolean) line: 270 06: SslErrorHandler.checkSslPrefTable(LoadListener, SslError) line: 165 07: Network.checkSslPrefTable(LoadListener, SslError) line: 336 08: LoadListener.handleSslErrorRequest(SslError) line: 814 09: HttpsConnection.openConnection(Request) line: 325 10: HttpsConnection(Connection).openHttpConnection(Request) line: 407 11: HttpsConnection(Connection).processRequests(Request) line: 260 12: ConnectionThread.run() line: 134
ところが、別スレッドからロードが既にキャンセルされてしまっていた場合は5行目の SslErrorHandler.handleSslErrorResponse() の中が以下のようになっているので、これ以降のメソッドは呼び出されず、restartConnection()が呼ばれることはない。
01: synchronized void handleSslErrorResponse(・・・) { 02: if (!loader.cancelled()) { // ←ロードが既にキャンセルされていればスキップ 03: ・・・ 04: loader.handleSslErrorResponse(proceed); 05: ・・・ 06: } 07: }
では、キャンセルの処理の中でrestartConnection()が呼ばれるかというと・・・呼ばれません! キャンセル処理の中ではcloseConnection()が以下のように呼ばれる。
01: HttpsConnection.closeConnection() line: 385 02: HttpsConnection(Connection).cancel() line: 159 03: Request.cancel() line: 384 04: RequestHandle.cancel() line: 99 05: LoadListener.cancel() line: 1297 06: JWebCoreJavaBridge.sharedTimerFired() line: not available [native method] 07: JWebCoreJavaBridge.fireSharedTimer() line: 88 08: JWebCoreJavaBridge.handleMessage(Message) line: 105 09: JWebCoreJavaBridge(Handler).dispatchMessage(Message) line: 99 10: Looper.loop() line: 123 11: WebViewCore$WebCoreThread.run() line: 626 12: Thread.run() line: 1096
が、closeConnection()の処理は
01: void closeConnection() { 02: ・・・ 03: if (mSuspended) { 04: restartConnection(false); 05: ・・・ 06: }
となっていて、mSuspended が true になる前に呼ばれても restartConnection() は呼ばれず mSuspended も false にはならない。
つまり、上記HttpsConnection.openConnection() の2行目辺りで通信中にユーザーが何かリンクをタップすると、そのスレッドは10分間のwaitに入ってしまい、使用不能となる。これが、http0〜3全てのスレッドで起きるとリンクをタップしても通信しないと言う症状が発生する。
Dropbox API を curl で叩いてみる
curl で Dropbox の REST API を叩いてみる。
Dropbox APIのドキュメントはこちら
Dropbox Mobile API Documentation
Dropbox APIのドキュメントにはOAuthに関することは何も書かれていないので以下の文書を参考にした。
OAuth Core 1.0 Revision A 日本語訳
アクセストークン/シークレットを取得
仮に、Dropboxのアカウントとアプリ用のConsumer key/secretが以下のようだったとする。
- メールアドレス => foobar@foobar12345.com
- パスワード => foobarpass
- Consumer key => hogehoge
- Consumer secret => fugafuga
まず以下のようにして /token APIからアクセストークン/シークレットを取得
curl -s 'https://api.dropbox.com/0/token?email=foobar@foobar12345.com&password=foobarpass&oauth_consumer_key=hogehoge'
レスポンスはこんな感じ。フォーマットはJSON
{"token": "piyopiyo", "secret": "puyopuyo"} (値はダミー)
これを利用して、その他のAPIもアクセス可能となる。
API にアクセスしてみよう
試しに /account/info APIにアクセスしてみる。
URLはこれ
https://api.dropbox.com/0/account/info
このURLに署名付きリクエストを送る。
署名をするためのベース文字列を生成
OAuth Core 1.0 Revision A 日本語訳 の9.1章参照
出来上がった署名ベース文字列はこちら
GET&https%3A%2F%2Fapi.dropbox.com%2F0%2Faccount%2Finfo&oauth_consumer_key%3Dgly1ffa07jpan24%26oauth_nonce%3DM6mvq3I0d4%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1295511698%26oauth_token%3Dpiyopiyo
ベース文字列に署名をする
OAuth Core 1.0 Revision A 日本語訳 の9.2章参照
署名にはopensslを使用すればOK
echo -n 'GET&https%3A%2F%2Fapi.dropbox.com%2F0%2Faccount%2Finfo&oauth_consumer_key%3Dgly1ffa07jpan24%26oauth_nonce%3DM6mvq3I0d4%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1295511698%26oauth_token%3Dpiyopiyo' | openssl sha1 -hmac 'fugafuga&puyopuyo' -binary | openssl base64 | sed 's/\//%2F/g' | sed 's/=/%3D/g' | sed 's/+/%2B/g'
出力はこんな感じ。これが署名。
syi0AHTbMuVrFYCw5lcYNFuEOXs%3D
Authorizationヘッダの生成
OAuth Core 1.0 Revision A 日本語訳 の5.4章参照
できあがったAuthorizationヘッダがこれ
Authorization: OAuth oauth_consumer_key="hogehoge",oauth_token="piyopiyo",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1295511698",oauth_nonce="M6mvq3I0d4",oauth_signature="syi0AHTbMuVrFYCw5lcYNFuEOXs%3D" (oauth_versionは無くても行けた)
APIにアクセス
ここまで来れば後はAuthorizationヘッダをくっつけて、APIのURLにリクエストを投げるだけ
curl -H 'Authorization: OAuth oauth_consumer_key="hogehoge",oauth_token="piyopiyo",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1295511698",oauth_nonce="M6mvq3I0d4",oauth_signature="syi0AHTbMuVrFYCw5lcYNFuEOXs%3D"' 'https://api.dropbox.com/0/account/info'
するとこのようなレスポンスが返ってくる。
{"referral_link": "https://www.dropbox.com/referrals/◯◯◯◯◯", "display_name": "foo bar", "uid": 12345678, "country": "JP", "quota_info": {"shared": 0, "quota": 2415919104, "normal": 471546692}, "email": "foobar@foobar12345.com"}
パラメータを敢えて間違えるとエラーレスポンスも体験できる。
シェルスクリプトにしてみた
んー。シェルでやるもんじゃあないなぁ(笑)
連想配列のないbashとJSONは相性が悪い。
#!/bin/bash function _dbg() { #echo $1 return 0 } # # ランダム文字列生成 # randstring() { len=$1 rndstr="" while [ $(echo ${rndstr} | wc -m) -le $len ]; do rnd=$[RANDOM % 75 + 48] if [ ${rnd} -ge 58 -a ${rnd} -le 64 -o ${rnd} -ge 91 -a ${rnd} -le 96 ]; then continue fi rndstr=${rndstr}$(printf \\x$(printf %x ${rnd})) done echo ${rndstr} return 0 } # # 状態スタックに状態をプッシュ # function push_stat() { _STAT[${#_STAT[@]}]=$1 _dbg NUM_STAT=${#_STAT[@]}, _STAT=${_STAT[*]} return 0 } # # 状態スタックから状態をポップ # function pop_stat() { unset _STAT[`expr ${#_STAT[@]} - 1`] _dbg NUM_STAT=${#_STAT[@]}, _STAT=${_STAT[*]} return 0 } # # 現在の状態を取得 # function top_stat() { local num=${#_STAT[@]} if [ $num -gt 0 ]; then echo ${_STAT[(($num-1))]} else _dbg "" fi return 0 } # # JSON解析君 # JSONを標準入力から読み取り # JSON_<obj1>_<obj2>_<prop1>=<value> # のような文字列を標準出力に出力するのでevalしてくだしあ # 呼び出し前には unset ${!JSON_*} しておくこと # function json() { local ST_OBJ=0 local ST_KEY=1 local ST_STR=2 local ST_VALUE=3 local KEY="JSON" local VALUE="" local LINE local CHAR push_stat $ST_VALUE while read LINE; do for((i=0; i < ${#LINE}; i++)); do CHAR=${LINE:$i:1} _dbg $CHAR case `top_stat` in $ST_KEY) case $CHAR in \") push_stat $ST_STR _dbg "str start" ;; \:) pop_stat _dbg "key end" _dbg key=$VALUE KEY=$VALUE unset VALUE push_stat $ST_VALUE ;; esac ;; $ST_STR) case $CHAR in \") pop_stat _dbg "str end" ;; " ") ;; *) VALUE=${VALUE}$CHAR ;; esac ;; $ST_VALUE) case $CHAR in \") push_stat $ST_STR _dbg "str start" ;; \,|\}) pop_stat _dbg "value end" _dbg value=$VALUE if [ -n "$KEY" ]; then PROP=`echo ${OBJPATH[@]} | sed -e 's/ /_/g'` if [ -n $PROP ]; then PROP=${PROP}_$KEY fi _dbg "$PROP=$VALUE" echo "$PROP=$VALUE" #eval "$PROP=$VALUE" fi unset VALUE unset KEY case $CHAR in \,) push_stat $ST_KEY _dbg "key start" ;; \}) pop_stat _dbg "object end" unset OBJPATH[`expr ${#OBJPATH[@]} - 1`]; ;; esac ;; \{) push_stat $ST_OBJ _dbg object start # obj start OBJPATH[${#OBJPATH[@]}]=$KEY push_stat $ST_KEY _dbg key start ;; " ") ;; *) VALUE=${VALUE}$CHAR ;; esac ;; esac done done } #main MAIL=$1 PASS=$2 CKEY=$3 CSECRET=$4 if [ -z "$CKEY" -o -z "$CSECRET" -o -z "$MAIL" -o -z "$PASS" ]; then echo usage: `basename $0` '<mail address> <password> <consumer key> <consumer secret>' >&2 exit 1 fi echo retrieve Access token/secret form Dropbox.. TOKEN_RESP=`curl -s "https://api.dropbox.com/0/token?email=$MAIL&password=$PASS&oauth_consumer_key=$CKEY"` echo $TOKEN_RESP unset ${!JSON_*} eval `echo $TOKEN_RESP | json` ATOKEN=$JSON_token ASECRET=$JSON_secret echo "Access token: $ATOKEN" echo "Access token secret: $ASECRET" if [ -z "$ATOKEN" -o -z "$ASECRET" ]; then echo "Can't retrieve Access token/secret" >&2 exit 1 fi NONCE=`randstring 10` TIMESTAMP=`date +%s` echo "creating OAuth Signature Base String.." SIGN_BASE='GET&https%3A%2F%2Fapi.dropbox.com%2F0%2Faccount%2Finfo&oauth_consumer_key%3D'$CKEY'%26oauth_nonce%3D'$NONCE'%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D'$TIMESTAMP'%26oauth_token%3D'$ATOKEN echo "Signature Base String: $SIGN_BASE" echo "Signing.." SIGN=`echo -n $SIGN_BASE | openssl sha1 -hmac $CSECRET'&'$ASECRET -binary | openssl base64 | sed 's/\//%2F/g' | sed 's/=/%3D/g' | sed 's/+/%2B/g'` echo "Signature: $SIGN" echo "Retrieving account info..." RESP=`curl -s -H 'Authorization: OAuth oauth_consumer_key="'$CKEY'",oauth_token="'$ATOKEN'",oauth_signature_method="HMAC-SHA1",oauth_timestamp="'$TIMESTAMP'",oauth_nonce="'$NONCE'",oauth_signature="'$SIGN'"' 'https://api.dropbox.com/0/account/info'` echo $RESP unset ${!JSON_*} eval `echo $RESP | json` for p in ${!JSON_*}; do eval echo $p=\$$p done
使い方
$ dropbox_api.sh
Dropboxアプリ制作の流れ
Dropbox API(REST API over HTTP)を利用するAndroidアプリを製作したときのメモ(Android以外も多分同様)
大まかな流れ
ディベロッパー登録
通常のDropboxのホーム画面の下に開発者用ページへのリンクがある。
ここからディベロッパーホームへ行き、左のメニューから「MyApps」を選ぶと初回だけディベロッパー規約の同意が求められる
同意するとアプリ一覧画面になる。
製作するアプリの情報を登録
アプリ一覧画面から「Create an App」を選択。
アプリ名と説明を入力
これでアプリ一覧画面に今登録したアプリが表示される。アプリの詳細画面に行くと OAuth の Consumer Key と Consumer Secret が確認できる。開発にSDKを使用する場合は OAuth の部分はSDKがラップしているので OAuth を知らなくても大丈夫。
SDKのダウンロード、開発、テスト
ディベロッパー用ホームのメニューに「Client Libraries」というのがあるのでそこからSDKをダウンロード出来る。SDKのドキュメントは無い(笑) SDK に同胞されているサンプルコードやライブラリのソースコードを見るか、ディベロッパーフォーラムを検索する。ググるのも吉。
アプリの公開申請
アプリ詳細画面 => Options => 「Apply for production status」で表示されるフォームに記入。企業用みたいなフォームだが個人でも大丈夫。
この申請を行うとどうなるのか・・・
This will submit your app to our reviewers. Our reviewers will be looking to make sure your application adheres to the API terms and conditions and branding guidelines, ensure your company URL and email is correct, and meets the most basic technical/performance standards (ie. it works).
ということで、レビュアーに送られて規約に準拠しているかや、動作に問題がないかなどが検証されるそうです。が、私の場合は申請後は承諾されるまで一切連絡はありませんでした。
そして
50日後
にようやく「申請通ったよ」のメールが来ました(笑)
動的ライブラリの観察その4
前回は、動的ライブラリの関数へのシンボル参照は、/usr/lib/libSystem.B.dylib の中の dyld_stub_binder の中で解決された〜、というところまで見た。今回は dyld_stub_binder の中で何が起こっているのかをもう少し見てみる。
ソースは前回と同じ。
動的ライブラリはこれ
$ cat libfoo1.c int do_foo(int i) { return i; }
$ gcc -dynamiclib -o libfoo1.dylib libfoo1.c
動的ライブラリを呼び出す方はこれ
$ cat hoge1.c int do_foo(int); int main(int argc, char **argv) { do_foo(5); do_foo(6); return 0; }
$ gcc -L./ -lfoo1 -o hoge1 hoge1.c
gdb で hoge1 を実行。
$gdb hoge1
前回までのおさらい・・・・は前回、前々回参照ということで。dyld_stub_binder は dyld_stub_binder.s で定義されているアセンブリコード。
最初の do_foo(int) 呼び出し直後の dyld_stub_binder でブレイクしてディスアセンブルしてみる。
こんな感じ(抜粋)
0x00007fff8020bfa8 <dyld_stub_binder+0>: push %rbp 0x00007fff8020bfa9 <dyld_stub_binder+1>: mov %rsp,%rbp 0x00007fff8020bfac <dyld_stub_binder+4>: sub $0xc0,%rsp (レジスタの退避) 0x00007fff8020c011 <dyld_stub_binder+105>: mov 0x8(%rbp),%rdi 0x00007fff8020c015 <dyld_stub_binder+109>: mov 0x10(%rbp),%rsi 0x00007fff8020c019 <dyld_stub_binder+113>: callq 0x7fff8010fc4f <_Z21_dyld_fast_stub_entryPvl> 0x00007fff8020c01e <dyld_stub_binder+118>: mov %rax,%r11 (レジスタの復帰) 0x00007fff8020c07f <dyld_stub_binder+215>: add $0xc0,%rsp 0x00007fff8020c086 <dyld_stub_binder+222>: pop %rbp 0x00007fff8020c087 <dyld_stub_binder+223>: add $0x10,%rsp 0x00007fff8020c08b <dyld_stub_binder+227>: jmpq *%r11
前回はこの _Z21_dyld_fast_stub_entryPvl の中で do_foo(int)のアドレスが解決されて戻り値として戻ってきているなと言うところまでしか見ていなかった。今回はこの先をもうちょっと観てみよう。
このときのvmmapは以下のように成っている。
__TEXT 0000000100000000-0000000100001000 [ 4K] r-x/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/hoge1 __DATA 0000000100001000-0000000100002000 [ 4K] rw-/rwx SM=PRV /Users/teru/Documents/low_level/blog_dylib2/hoge1 __LINKEDIT 0000000100002000-0000000100003000 [ 4K] r--/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/hoge1 __TEXT 0000000100003000-0000000100004000 [ 4K] r-x/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/libfoo1.dylib __LINKEDIT 0000000100004000-0000000100005000 [ 4K] r--/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/libfoo1.dylib STACK GUARD 0000000100005000-0000000100006000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100006000-0000000100007000 [ 4K] rw-/rwx SM=COW STACK GUARD 0000000100007000-0000000100009000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100009000-0000000100014000 [ 44K] rw-/rwx SM=COW STACK GUARD 0000000100014000-0000000100016000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100016000-0000000100021000 [ 44K] rw-/rwx SM=PRV STACK GUARD 0000000100021000-0000000100022000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100022000-0000000100023000 [ 4K] r--/rwx SM=COW MALLOC_TINY 0000000100100000-0000000100200000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x100006000 STACK GUARD 00007fff5bc00000-00007fff5f400000 [ 56.0M] ---/rwx SM=NUL Stack 00007fff5f400000-00007fff5fbfd000 [ 8180K] rw-/rwx SM=ZER Stack 00007fff5fbfd000-00007fff5fbfe000 [ 4K] rw-/rwx SM=PRV Stack 00007fff5fbfe000-00007fff5fbff000 [ 4K] rw-/rwx SM=ZER Stack 00007fff5fbff000-00007fff5fc00000 [ 4K] rw-/rwx SM=COW thread 0 __TEXT 00007fff5fc00000-00007fff5fc04000 [ 16K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc04000-00007fff5fc05000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc05000-00007fff5fc0a000 [ 20K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc0a000-00007fff5fc0b000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc0b000-00007fff5fc3c000 [ 196K] r-x/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc3c000-00007fff5fc41000 [ 20K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc41000-00007fff5fc43000 [ 8K] rw-/rwx SM=ZER /usr/lib/dyld __DATA 00007fff5fc43000-00007fff5fc44000 [ 4K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc44000-00007fff5fc7b000 [ 220K] rw-/rwx SM=ZER /usr/lib/dyld __LINKEDIT 00007fff5fc7b000-00007fff5fc8f000 [ 80K] r--/rwx SM=COW /usr/lib/dyld __DATA 00007fff7001a000-00007fff7003d000 [ 140K] rw-/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff80103000-00007fff8020b000 [ 1056K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff8020b000-00007fff8020c000 [ 4K] r-x/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff8020b000-00007fff803cb000 [ 1792K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff86b99000-00007fff86b9e000 [ 20K] r-x/r-x SM=COW /usr/lib/system/libmathCommon.A.dylib __LINKEDIT 00007fff870f6000-00007fff88627000 [ 21.2M] r--/r-- SM=COW /usr/lib/system/libmathCommon.A.dylib
_Z21_dyld_fast_stub_entryPvl のアドレス 0x7fff8010fc4f は libSystem.B.dylib に含まれていることがわかる。_Z21_dyld_fast_stub_entryPvl というのはデマングルすると _dyld_fast_stub_entry(void*, long) というC++の関数で dyldAPIsInLibSystem.cpp で定義されている。
dyld_stub_binder.s もそうだが、dyldのソースに記述されているコードがなぜ libSystem.B.dylib に含まれているのかは謎。どこかで LibSystem は Umbrella Framework だからどうのこうのという記述を見た気がするが・・・。まぁ、そのうち調べよう。
C++の関数はこう
#if __i386__ || __x86_64__ __attribute__((visibility("hidden"))) void* _dyld_fast_stub_entry(void* loadercache, long lazyinfo) { DYLD_NO_LOCK_THIS_BLOCK; static void* (*p)(void*, long) = NULL; if(p == NULL) _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p); return p(loadercache, lazyinfo); } #endif
実行中の物をディスアセンブルしたものはこう
(gdb) b _Z21_dyld_fast_stub_entryPvl Breakpoint 4 at 0x7fff8010fc5f (gdb) c Continuing. Breakpoint 4, 0x00007fff8010fc5f in _dyld_fast_stub_entry () (gdb) disas Dump of assembler code for function _Z21_dyld_fast_stub_entryPvl: 0x00007fff8010fc4f <_Z21_dyld_fast_stub_entryPvl+0>: push %rbp 0x00007fff8010fc50 <_Z21_dyld_fast_stub_entryPvl+1>: mov %rsp,%rbp 0x00007fff8010fc53 <_Z21_dyld_fast_stub_entryPvl+4>: mov %rbx,-0x10(%rbp) 0x00007fff8010fc57 <_Z21_dyld_fast_stub_entryPvl+8>: mov %r12,-0x8(%rbp) 0x00007fff8010fc5b <_Z21_dyld_fast_stub_entryPvl+12>: sub $0x10,%rsp 0x00007fff8010fc5f <_Z21_dyld_fast_stub_entryPvl+16>: mov %rdi,%r12 0x00007fff8010fc62 <_Z21_dyld_fast_stub_entryPvl+19>: mov %rsi,%rbx 0x00007fff8010fc65 <_Z21_dyld_fast_stub_entryPvl+22>: cmpq $0x0,-0x100d9525(%rip) # 0x7fff70036748 <_Zz21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc6d <_Z21_dyld_fast_stub_entryPvl+30>: jne 0x7fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51> 0x00007fff8010fc6f <_Z21_dyld_fast_stub_entryPvl+32>: lea -0x100d952e(%rip),%rsi # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc76 <_Z21_dyld_fast_stub_entryPvl+39>: lea 0x143f93(%rip),%rdi # 0x7fff80253c10 <commonCryptoVersionString+96> 0x00007fff8010fc7d <_Z21_dyld_fast_stub_entryPvl+46>: callq 0x7fff80109ffa <_dyld_func_lookup> 0x00007fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51>: mov %rbx,%rsi 0x00007fff8010fc85 <_Z21_dyld_fast_stub_entryPvl+54>: mov %r12,%rdi 0x00007fff8010fc88 <_Z21_dyld_fast_stub_entryPvl+57>: mov -0x100d9547(%rip),%r11 # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc8f <_Z21_dyld_fast_stub_entryPvl+64>: mov (%rsp),%rbx 0x00007fff8010fc93 <_Z21_dyld_fast_stub_entryPvl+68>: mov 0x8(%rsp),%r12 0x00007fff8010fc98 <_Z21_dyld_fast_stub_entryPvl+73>: leaveq 0x00007fff8010fc99 <_Z21_dyld_fast_stub_entryPvl+74>: jmpq *%r11 End of assembler dump.
余談だが、_Z21_dyld_fast_stub_entryPvl+22 にちょっと注目。ここは C++ の if(p == NULL) に該当するところだが、0x7fff70036748 というアドレスから、static なローカル変数 p はスタックではなくて libSystem.B.dylib の __DATA セグメントに配置されている。static(静的)ってこういう事なんですね〜。
_dyld_func_lookup は dyldLibSystemGlue.c で定義されている
struct __DATA__dyld { long lazy; int (*lookup)(const char*, void**); }; static struct __DATA__dyld myDyldSection __attribute__ ((section ("__DATA,__dyld"))) = { 0, NULL }; __attribute__((weak, visibility("hidden"))) int _dyld_func_lookup(const char* dyld_func_name, void **address) { return (*myDyldSection.lookup)(dyld_func_name, address); }
実行中の物をディスアセンブルしたものはこう。アドレスからこれも libSystem.B.dylib 内のコードだな。
0x00007fff80109ffa <_dyld_func_lookup+0>: push %rbp 0x00007fff80109ffb <_dyld_func_lookup+1>: mov %rsp,%rbp 0x00007fff80109ffe <_dyld_func_lookup+4>: mov -0x100ecb9d(%rip),%r11 # 0x7fff7001d468 <Mydyldsection+8> 0x00007fff8010a005 <_dyld_func_lookup+11>: leaveq 0x00007fff8010a006 <_dyld_func_lookup+12>: jmpq *%r11 0x00007fff8010a009 <_dyld_func_lookup+15>: nop
Cのソースと見比べると myDyldSection.lookup のアドレスは 0x7fff7001d468 だ。
そこにはどんな値が入っているのかというと
(gdb) x /1xg 0x7fff7001d468 0x7fff7001d468: 0x00007fff5fc01008
vmmapから /usr/lib/dyld 内のアドレスで有ることがわかる。このあたりから制御がdyldに移るようだ。内容はこう。
(gdb) disas 0x00007fff5fc01008 Dump of assembler code for function __dyld_dyld_func_lookup: 0x00007fff5fc01008 <__dyld_dyld_func_lookup+0>: jmpq 0x7fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm> 0x00007fff5fc0100d <__dyld_dyld_func_lookup+5>: nop 0x00007fff5fc0100e <__dyld_dyld_func_lookup+6>: nop 0x00007fff5fc0100f <__dyld_dyld_func_lookup+7>: nop
該当するソースは dyldStartup.s というアセンブリコード。このアドレスがどの様にして上記の myDyldSection.lookup に代入されたのかは謎。処理内容は __dyld__Z18lookupDyldFunctionPKcPm、つまり lookupDyldFunction(char const*, unsigned long*) へジャンプしてるだけ。
lookupDyldFunction(char const*, unsigned long*) は dyldAPIs.cpp でこのように定義されている。
bool lookupDyldFunction(const char* name, uintptr_t* address) { for (const dyld_func* p = dyld_funcs; p->name != NULL; ++p) { if ( strcmp(p->name, name) == 0 ) { if( p->implementation == unimplemented ) dyld::log("unimplemented dyld function: %s\n", p->name); *address = (uintptr_t)p->implementation; return true; } } *address = 0; return false; }
dyld_funcs は dyldAPIs.cpp で定義されているこのようなテーブルで、 dyld が提供するAPI名と実装する関数のアドレスを対応付けるテーブルだ。
struct dyld_func { const char* name; void* implementation; }; static struct dyld_func dyld_funcs[] = { {"__dyld_register_func_for_add_image", (void*)_dyld_register_func_for_add_image }, {"__dyld_register_func_for_remove_image", (void*)_dyld_register_func_for_remove_image }, ・・・ #if !__arm__ {"__dyld_find_unwind_sections", (void*)client_dyld_find_unwind_sections }, #endif #if __i386__ || __x86_64__ {"__dyld_fast_stub_entry", (void*)dyld::fastBindLazySymbol }, #endif {"__dyld_image_path_containing_address", (void*)dyld_image_path_containing_address }, ・・・ {NULL, 0} };
で、今は何というAPIを探してるんでしたっけ?というと _dyld_fast_stub_entry(void* loadercache, long lazyinfo) の中で _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p) というふうに呼び出されているので __dyld_fast_stub_entry だ。
一応確認。関数の第一引数はrdiレジスタなので、lookupDyldFunction() まで実行してから以下のように確認。
(gdb) i reg rip rip 0x7fff5fc07e43 0x7fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm> (gdb) x /1s $rdi 0x7fff80253c10: "__dyld_fast_stub_entry"
確かに __dyld_fast_stub_entry を探している。
上記のテーブルによると、その実装は dyld::fastBindLazySymbol となっている。これもデバッガで確認してみる。lookupDyldFunction() をディスアセンブルするとこうなる。
(gdb) disas Dump of assembler code for function __dyld__Z18lookupDyldFunctionPKcPm: 0x00007fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm+0>: push %rbp 0x00007fff5fc07e44 <__dyld__Z18lookupDyldFunctionPKcPm+1>: mov %rsp,%rbp 0x00007fff5fc07e47 <__dyld__Z18lookupDyldFunctionPKcPm+4>: push %r14 0x00007fff5fc07e49 <__dyld__Z18lookupDyldFunctionPKcPm+6>: push %r13 0x00007fff5fc07e4b <__dyld__Z18lookupDyldFunctionPKcPm+8>: push %r12 0x00007fff5fc07e4d <__dyld__Z18lookupDyldFunctionPKcPm+10>: push %rbx 0x00007fff5fc07e4e <__dyld__Z18lookupDyldFunctionPKcPm+11>: mov %rdi,%r13 0x00007fff5fc07e51 <__dyld__Z18lookupDyldFunctionPKcPm+14>: mov %rsi,%r14 0x00007fff5fc07e54 <__dyld__Z18lookupDyldFunctionPKcPm+17>: lea 0x35945(%rip),%r12 # 0x7fff5fc3d7a0 <__dyld__ZL10dyld_funcs> 0x00007fff5fc07e5b <__dyld__Z18lookupDyldFunctionPKcPm+24>: jmp 0x7fff5fc07e9e <__dyld__Z18lookupDyldFunctionPKcPm+91> 0x00007fff5fc07e5d <__dyld__Z18lookupDyldFunctionPKcPm+26>: mov %r13,%rsi 0x00007fff5fc07e60 <__dyld__Z18lookupDyldFunctionPKcPm+29>: mov %rbx,%rdi 0x00007fff5fc07e63 <__dyld__Z18lookupDyldFunctionPKcPm+32>: callq 0x7fff5fc22fb0 <__dyld_strcmp> 0x00007fff5fc07e68 <__dyld__Z18lookupDyldFunctionPKcPm+37>: test %eax,%eax 0x00007fff5fc07e6a <__dyld__Z18lookupDyldFunctionPKcPm+39>: jne 0x7fff5fc07e9a <__dyld__Z18lookupDyldFunctionPKcPm+87> 0x00007fff5fc07e6c <__dyld__Z18lookupDyldFunctionPKcPm+41>: lea 0x23b(%rip),%rax # 0x7fff5fc080ae <__dyld__ZL13unimplementedv> 0x00007fff5fc07e73 <__dyld__Z18lookupDyldFunctionPKcPm+48>: cmp %rax,0x8(%r12) 0x00007fff5fc07e78 <__dyld__Z18lookupDyldFunctionPKcPm+53>: jne 0x7fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72> 0x00007fff5fc07e7a <__dyld__Z18lookupDyldFunctionPKcPm+55>: mov %rbx,%rsi 0x00007fff5fc07e7d <__dyld__Z18lookupDyldFunctionPKcPm+58>: lea 0x1e184(%rip),%rdi # 0x7fff5fc26008 0x00007fff5fc07e84 <__dyld__Z18lookupDyldFunctionPKcPm+65>: xor %eax,%eax 0x00007fff5fc07e86 <__dyld__Z18lookupDyldFunctionPKcPm+67>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72>: mov 0x8(%r12),%rax 0x00007fff5fc07e90 <__dyld__Z18lookupDyldFunctionPKcPm+77>: mov %rax,(%r14) 0x00007fff5fc07e93 <__dyld__Z18lookupDyldFunctionPKcPm+80>: mov $0x1,%eax 0x00007fff5fc07e98 <__dyld__Z18lookupDyldFunctionPKcPm+85>: jmp 0x7fff5fc07eb0 <__dyld__Z18lookupDyldFunctionPKcPm+109> 0x00007fff5fc07e9a <__dyld__Z18lookupDyldFunctionPKcPm+87>: add $0x10,%r12 0x00007fff5fc07e9e <__dyld__Z18lookupDyldFunctionPKcPm+91>: mov (%r12),%rbx 0x00007fff5fc07ea2 <__dyld__Z18lookupDyldFunctionPKcPm+95>: test %rbx,%rbx 0x00007fff5fc07ea5 <__dyld__Z18lookupDyldFunctionPKcPm+98>: jne 0x7fff5fc07e5d <__dyld__Z18lookupDyldFunctionPKcPm+26> 0x00007fff5fc07ea7 <__dyld__Z18lookupDyldFunctionPKcPm+100>: movq $0x0,(%r14) 0x00007fff5fc07eae <__dyld__Z18lookupDyldFunctionPKcPm+107>: xor %eax,%eax 0x00007fff5fc07eb0 <__dyld__Z18lookupDyldFunctionPKcPm+109>: pop %rbx 0x00007fff5fc07eb1 <__dyld__Z18lookupDyldFunctionPKcPm+110>: pop %r12 0x00007fff5fc07eb3 <__dyld__Z18lookupDyldFunctionPKcPm+112>: pop %r13 0x00007fff5fc07eb5 <__dyld__Z18lookupDyldFunctionPKcPm+114>: pop %r14 0x00007fff5fc07eb7 <__dyld__Z18lookupDyldFunctionPKcPm+116>: leaveq 0x00007fff5fc07eb8 <__dyld__Z18lookupDyldFunctionPKcPm+117>: retq End of assembler dump.
C++のソースと見比べると
*address = (uintptr_t)p->implementation;
にあたる部分は
0x00007fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72>: mov 0x8(%r12),%rax 0x00007fff5fc07e90 <__dyld__Z18lookupDyldFunctionPKcPm+77>: mov %rax,(%r14) 0x00007fff5fc07e93 <__dyld__Z18lookupDyldFunctionPKcPm+80>: mov $0x1,%eax
の部分なのでこのraxの値を確認してみる。(__dyld_fast_stub_entry を探しているのでこのコードを通ることは明らか。)
(gdb) b *0x00007fff5fc07e93 Breakpoint 6 at 0x7fff5fc07e93 (gdb) c Continuing. Breakpoint 6, 0x00007fff5fc07e93 in __dyld__Z18lookupDyldFunctionPKcPm () (gdb) i reg rax rax 0x7fff5fc04721 140734799824673 (gdb) x /3i $rax 0x7fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm>: push %rbp 0x7fff5fc04722 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+1>: mov %rsp,%rbp 0x7fff5fc04725 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+4>: push %rbx
という感じで、dyld::fastBindLazySymbol(ImageLoader**, unsigned long) のアドレスが予想通り取得されている。で、このアドレスが _dyld_fast_stub_entry() の p に代入されると。p は static 変数なので以降の呼び出しでは この dyld_func_lookup() の呼び出しは行われない。
ここでちょっと整理。dyld_stub_binder → _dyld_fast_stub_entry(void*, long) → _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p) で p に dyld::fastBindLazySymbol(ImageLoader**, unsigned long) のアドレスが取得される ← いまここ
で、_dyld_fast_stub_entry(void*, long) の
return p(loadercache, lazyinfo);
という部分で、dyld::fastBindLazySymbol(ImageLoader**, unsigned long) が呼び出される。
dyld::fastBindLazySymbol(ImageLoader**, unsigned long) は dyld.cpp で次のように定義されている。
#if COMPRESSED_DYLD_INFO_SUPPORT uintptr_t fastBindLazySymbol(ImageLoader** imageLoaderCache, uintptr_t lazyBindingInfoOffset) { uintptr_t result = 0; // get image if ( *imageLoaderCache == NULL ) { // save in cache *imageLoaderCache = dyld::findMappedRange((uintptr_t)imageLoaderCache); if ( *imageLoaderCache == NULL ) { const char* message = "fast lazy binding from unknown image"; dyld::log("dyld: %s\n", message); halt(message); } } // bind lazy pointer and return it try { result = (*imageLoaderCache)->doBindFastLazySymbol(lazyBindingInfoOffset, gLinkContext); } catch (const char* message) { dyld::log("dyld: lazy symbol binding failed: %s\n", message); halt(message); } // return target address to glue which jumps to it with real parameters restored return result; } #endif // COMPRESSED_DYLD_INFO_SUPPORT
ここで、imageLoaderCache と lazyBindingInfoOffset がどのような値であったかを復習。_dyld_fast_stub_entry() の中で dyld::fastBindLazySymbol が呼び出される場所は
0x00007fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51>: mov %rbx,%rsi 0x00007fff8010fc85 <_Z21_dyld_fast_stub_entryPvl+54>: mov %r12,%rdi 0x00007fff8010fc88 <_Z21_dyld_fast_stub_entryPvl+57>: mov -0x100d9547(%rip),%r11 # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p>
の部分なので _Z21_dyld_fast_stub_entryPvl+57 でブレイクして rdi(第一引数) と rsi(第二引数) を確認。
(gdb) i reg rip rip 0x7fff8010fc85 0x7fff8010fc85 <_dyld_fast_stub_entry(void*, long)+54> (gdb) i reg rdi rsi rdi 0x100001008 4294971400 rsi 0x0 0
この値はどこから来ているかというと、以下のようなスタブヘルパで設定されたもの。(詳しくは前回、前々回を参照)
0x100000f3a < stub helpers>: lea 0xc7(%rip),%r11 # 0x100001008 0x100000f41 < stub helpers+7>: push %r11 0x100000f43 < stub helpers+9>: jmpq *0xb7(%rip) # 0x100001000 0x100000f49 < stub helpers+15>: nop 0x100000f4a < stub helpers+16>: pushq $0x0 0x100000f4f < stub helpers+21>: jmpq 0x100000f3a < stub helpers> 0x100000f54 < stub helpers+26>: pushq $0xe 0x100000f59 < stub helpers+31>: jmpq 0x100000f3a < stub helpers>
この stub helpers+16 でプッシュされた 0x0 が lazyBindingInfoOffset、 stub helpers+7 でプッシュされた 0x100001008 が imageLoaderCache となっている。0x100001008 と言うのは hoge1 の __nl_symbol_ptr セクション内の2個目のノンレジーポインタのアドレス。
では、dyld::fastBindLazySymbol を観てみよう。実行中のものをディスアセンブルしたものはこれ。
Dump of assembler code for function __dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm: 0x00007fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+0>: push %rbp 0x00007fff5fc04722 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+1>: mov %rsp,%rbp 0x00007fff5fc04725 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+4>: push %rbx 0x00007fff5fc04726 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+5>: sub $0x8,%rsp 0x00007fff5fc0472a <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+9>: lea 0x3c26f(%rip),%rcx # 0x7fff5fc409a0 <__dyld__ZN4dyldL18sMappedRangesStartE> 0x00007fff5fc04731 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+16>: cmpq $0x0,(%rdi) 0x00007fff5fc04735 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+20>: jne 0x7fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79> 0x00007fff5fc04737 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+22>: jmpq 0x7fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160> 0x00007fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27>: mov (%rax,%rcx,1),%rdx 0x00007fff5fc04740 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+31>: test %rdx,%rdx 0x00007fff5fc04743 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+34>: je 0x7fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50> 0x00007fff5fc04745 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+36>: cmp 0x8(%rax,%rcx,1),%rdi 0x00007fff5fc0474a <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+41>: jb 0x7fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50> 0x00007fff5fc0474c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+43>: cmp 0x10(%rax,%rcx,1),%rdi 0x00007fff5fc04751 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+48>: jb 0x7fff5fc0476d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+76> 0x00007fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50>: add $0x18,%rax 0x00007fff5fc04757 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+54>: cmp $0x2580,%rax 0x00007fff5fc0475d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+60>: jne 0x7fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27> 0x00007fff5fc0475f <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+62>: mov 0x2580(%rcx),%rcx 0x00007fff5fc04766 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+69>: test %rcx,%rcx 0x00007fff5fc04769 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+72>: jne 0x7fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160> 0x00007fff5fc0476b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+74>: jmp 0x7fff5fc047c8 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+167> 0x00007fff5fc0476d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+76>: mov %rdx,(%rdi) 0x00007fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79>: mov (%rdi),%rdi 0x00007fff5fc04773 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+82>: mov (%rdi),%rax 0x00007fff5fc04776 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+85>: lea 0x3bd23(%rip),%rdx # 0x7fff5fc404a0 <__dyld__ZN4dyld12gLinkContextE> 0x00007fff5fc0477d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+92>: callq *0x110(%rax) 0x00007fff5fc04783 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+98>: jmp 0x7fff5fc047f0 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+207> 0x00007fff5fc04785 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+100>: mov %rax,%rbx 0x00007fff5fc04788 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+103>: dec %rdx 0x00007fff5fc0478b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+106>: jne 0x7fff5fc047b9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+152> 0x00007fff5fc0478d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+108>: mov %rax,%rdi 0x00007fff5fc04790 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+111>: callq 0x7fff5fc19751 <__dyld___cxa_begin_catch> 0x00007fff5fc04795 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+116>: mov %rax,%rbx 0x00007fff5fc04798 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+119>: mov %rax,%rsi 0x00007fff5fc0479b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+122>: lea 0x2119e(%rip),%rdi # 0x7fff5fc25940 0x00007fff5fc047a2 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+129>: xor %eax,%eax 0x00007fff5fc047a4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+131>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc047a9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+136>: mov %rbx,%rdi 0x00007fff5fc047ac <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+139>: callq 0x7fff5fc046cb <__dyld__ZN4dyld4haltEPKc> 0x00007fff5fc047b1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+144>: mov %rax,%rbx 0x00007fff5fc047b4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+147>: callq 0x7fff5fc197cf <__dyld___cxa_end_catch> 0x00007fff5fc047b9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+152>: mov %rbx,%rdi 0x00007fff5fc047bc <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+155>: callq 0x7fff5fc24b26 <__dyld__Unwind_Resume> 0x00007fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160>: xor %eax,%eax 0x00007fff5fc047c3 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+162>: jmpq 0x7fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27> 0x00007fff5fc047c8 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+167>: movq $0x0,(%rdi) 0x00007fff5fc047cf <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+174>: lea 0x21192(%rip),%rsi # 0x7fff5fc25968 0x00007fff5fc047d6 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+181>: lea 0x21154(%rip),%rdi # 0x7fff5fc25931 0x00007fff5fc047dd <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+188>: xor %eax,%eax 0x00007fff5fc047df <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+190>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc047e4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+195>: lea 0x2117d(%rip),%rdi # 0x7fff5fc25968 0x00007fff5fc047eb <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+202>: callq 0x7fff5fc046cb <__dyld__ZN4dyld4haltEPKc> 0x00007fff5fc047f0 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+207>: add $0x8,%rsp 0x00007fff5fc047f4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+211>: pop %rbx 0x00007fff5fc047f5 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+212>: leaveq 0x00007fff5fc047f6 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+213>: retq End of assembler dump.
*imageLoaderCacheの値は dyld::fastBindLazySymbol のアドレスである 0x7fff5fc04721 まで実行したところで次のようにして確認。
(gdb) i reg rip rip 0x7fff5fc04721 0x7fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm> (gdb) x /1xg $rdi 0x100001008: 0x0000000000000000
つまり *imageLoaderCache は NULL ですね。NULL なので dyld::findMappedRange が呼び出される。アセンブリを見てみると、この dyld::findMappedRange はインライン展開されていることがわかる。
dyld::findMappedRrange は dyld.cpp で次のように定義されている。
ImageLoader* findMappedRange(uintptr_t target) { for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { for (int i=0; i < MappedRanges::count; ++i) { if ( p->array[i].image != NULL ) { if ( (p->array[i].start <= target) && (target < p->array[i].end) ) return p->array[i].image; } } } return NULL; }
MappedRanges, sMappedRangesStart と言うのは dyld.cpp で次のように定義されている。
// The MappedRanges structure is used for fast address->image lookups. // The table is only updated when the dyld lock is held, so we don't // need to worry about multiple writers. But readers may look at this // data without holding the lock. Therefore, all updates must be done // in an order that will never cause readers to see inconsistent data. // The general rule is that if the image field is non-NULL then // the other fields are valid. // struct MappedRanges { enum { count=400 }; struct { ImageLoader* image; uintptr_t start; uintptr_t end; } array[count]; MappedRanges* next; }; static MappedRanges sMappedRangesStart;
あくまで推測だが、実行形式や動的ライブラリがロードされている場合、あるアドレス範囲へファイルをロードした ImageLoader というものが有って、それがこの構造体に格納されているということなのかもしれない。
であるとすれば、ここでの findMappedRange() 呼び出しは hoge1 の __DATA,__la_symbol_ptr をロードした ImageLoader を取得しているということになる。
sMappedRangesStart のアドレス、つまり __dyld__ZN4dyldL18sMappedRangesStartE のアドレスはディスアセンブル結果から 0x7fff5fc409a0 とわかるので少し内容を覗いてみる。
(gdb) x /30xg 0x7fff5fc409a0 0x7fff5fc409a0 <__dyld__ZN4dyldL18sMappedRangesStartE>: 0x00007fff5fc43c18 0x0000000100000000 0x7fff5fc409b0 <__dyld__ZN4dyldL18sMappedRangesStartE+16>: 0x0000000100003000 0x00007fff5fc43cb8 0x7fff5fc409c0 <__dyld__ZN4dyldL18sMappedRangesStartE+32>: 0x0000000100003000 0x0000000100005000 0x7fff5fc409d0 <__dyld__ZN4dyldL18sMappedRangesStartE+48>: 0x00007fff5fc43d88 0x00007fff80103000 0x7fff5fc409e0 <__dyld__ZN4dyldL18sMappedRangesStartE+64>: 0x00007fff802c3000 0x00007fff5fc43d88 0x7fff5fc409f0 <__dyld__ZN4dyldL18sMappedRangesStartE+80>: 0x00007fff7001a000 0x00007fff7003d000 0x7fff5fc40a00 <__dyld__ZN4dyldL18sMappedRangesStartE+96>: 0x00007fff5fc43d88 0x00007fff870f6000 0x7fff5fc40a10 <__dyld__ZN4dyldL18sMappedRangesStartE+112>: 0x00007fff889d9000 0x00007fff5fc43e20 0x7fff5fc40a20 <__dyld__ZN4dyldL18sMappedRangesStartE+128>: 0x00007fff86b99000 0x00007fff86b9e000 0x7fff5fc40a30 <__dyld__ZN4dyldL18sMappedRangesStartE+144>: 0x00007fff5fc43e20 0x00007fff870f6000 0x7fff5fc40a40 <__dyld__ZN4dyldL18sMappedRangesStartE+160>: 0x00007fff889d9000 0x0000000000000000 0x7fff5fc40a50 <__dyld__ZN4dyldL18sMappedRangesStartE+176>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a60 <__dyld__ZN4dyldL18sMappedRangesStartE+192>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a70 <__dyld__ZN4dyldL18sMappedRangesStartE+208>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a80 <__dyld__ZN4dyldL18sMappedRangesStartE+224>: 0x0000000000000000 0x0000000000000000
MappedRanges構造体として見ると ImageLoaderのアドレス、ロードした範囲の開始、終了、がそれっぽい数字で並んでいる。今は 0x100001008 をロードしたImageLoaderを探しているので、0x00007fff5fc43c18 が取得されるはず。
C++の
result = (*imageLoaderCache)->doBindFastLazySymbol(lazyBindingInfoOffset, gLinkContext);
は、アセンブリではここに該当。
0x00007fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79>: mov (%rdi),%rdi 0x00007fff5fc04773 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+82>: mov (%rdi),%rax 0x00007fff5fc04776 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+85>: lea 0x3bd23(%rip),%rdx # 0x7fff5fc404a0 <__dyld__ZN4dyld12gLinkContextE> 0x00007fff5fc0477d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+92>: callq *0x110(%rax)
C++ではインスタンスメソッド呼び出しの第一引数(rdi)はインスタンスのアドレスなので callq 時の rdi の値を見れば取得された ImageLoader のアドレスを確認できる。
(gdb) x /1x $rdi 0x100001008: 0x00007fff5fc43c18 (gdb) b *0x00007fff5fc0477d Breakpoint 8 at 0x7fff5fc0477d (gdb) c Continuing. Breakpoint 8, 0x00007fff5fc0477d in __dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm () (gdb) i reg rdi rdi 0x7fff5fc43c18 140734800083992
予想通り 0x7fff5fc43c18 が取得されている。
次に、 ImageLoader::doBindFastLazySymbol の呼出なわけだが、これは ImageLoader.h で次のように定義されている。
// called at runtime when a fast lazily bound function is first called virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context) = 0;
純粋仮想関数。上記アセンブリでraxをゴニョゴニョしているのは、この実装を呼び出すために仮想関数テーブルがうんたらな処理をしているからだとおもう(仮想関数の仕組はまだよくわかりません。そのうち調べよう ^^;)
なのでここはサクっとgdbに頼って次へ進む。callq 先はこうなった。
0x00007fff5fc168a0 in __dyld__ZN26ImageLoaderMachOCompressed20doBindFastLazySymbolEjRKN11ImageLoader11LinkContextE ()
ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&) が呼び出されている。これは ImageLoaderMachOCompressed.cpp で次のように定義されている。
uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol( uint32_t lazyBindingInfoOffset, const LinkContext& context) { const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off; const uint8_t* const end = &start[fDyldInfo->lazy_bind_size]; if (lazyBindingInfoOffset > fDyldInfo->lazy_bind_size) throw "fast lazy bind offset out of range"; uint8_t type = BIND_TYPE_POINTER; uintptr_t address = 0; const char* symbolName = NULL; uint8_t symboFlags = 0; int libraryOrdinal = 0; bool done = false; uintptr_t result = 0; const uint8_t* p = &start[lazyBindingInfoOffset]; while (!done && (p < end)) { uint8_t immediate = *p & BIND_IMMEDIATE_MASK; uint8_t opcode = *p & BIND_OPCODE_MASK; ++p; switch (opcode) { case BIND_OPCODE_DONE: done = true; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: libraryOrdinal = immediate; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: libraryOrdinal = read_uleb128(p, end); break; case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: // the special ordinals are negative numbers if (immediate == 0) libraryOrdinal = 0; else { int8_t signExtended = BIND_OPCODE_MASK | immediate; libraryOrdinal = signExtended; } break; case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: symbolName = (char*) p; symboFlags = immediate; while (*p != '\0') ++p; ++p; break; case BIND_OPCODE_SET_TYPE_IMM: type = immediate; break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: if (immediate > fSegmentsCount) dyld::throwf( "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", immediate, fSegmentsCount); address = segActualLoadAddress(immediate) + read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: result = this->bindAt(context, address, type, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL); break; case BIND_OPCODE_SET_ADDEND_SLEB: case BIND_OPCODE_ADD_ADDR_ULEB: case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: default: dyld::throwf("bad lazy bind opcode %d", *p); } } return result; }
LinkContext、fLinkEditBase、fDyldInfo とは一体どういうデータなのか・・・。これを知るためにはローディングの仕組を理解する必要があるのかなぁ。もう少しで、動的ライブラリの関数がバインドされる瞬間を見れそうな気がするのだが・・・とりあえず今日はここまで。
動的ライブラリの観察その3
前回は動的ライブラリの関数が実行される様子の概要を観た。今回は詳細を観察してみる。
事前にhoge1のセクション一覧を取得しておく。(出力結果抜粋)
$otool -l hoge1 Section sectname __text segname __TEXT addr 0x0000000100000ec8 size 0x0000000000000066 Section sectname __symbol_stub1 segname __TEXT addr 0x0000000100000f2e size 0x000000000000000c Section sectname __stub_helper segname __TEXT addr 0x0000000100000f3a size 0x0000000000000024 Section sectname __unwind_info segname __TEXT addr 0x0000000100000f60 size 0x0000000000000054 Section sectname __eh_frame segname __TEXT addr 0x0000000100000fb8 size 0x0000000000000048 Section sectname __nl_symbol_ptr segname __DATA addr 0x0000000100001000 size 0x0000000000000010 Section sectname __la_symbol_ptr segname __DATA addr 0x0000000100001010 size 0x0000000000000010 Section sectname __program_vars segname __DATA addr 0x0000000100001020 size 0x0000000000000028 Section sectname __data segname __DATA addr 0x0000000100001048 size 0x0000000000000020
gdbで動的ライブラリの関数 do_foo(int) が呼び出される様子を観察。
まずは main(int, char **)
$gdb hoge1 (gdb) disas main Dump of assembler code for function main: 0x0000000100000f04 <main+0>: push %rbp 0x0000000100000f05 <main+1>: mov %rsp,%rbp 0x0000000100000f08 <main+4>: sub $0x10,%rsp 0x0000000100000f0c <main+8>: mov %edi,-0x4(%rbp) 0x0000000100000f0f <main+11>: mov %rsi,-0x10(%rbp) 0x0000000100000f13 <main+15>: mov $0x5,%edi 0x0000000100000f18 <main+20>: callq 0x100000f2e <dyld_stub_do_foo> 0x0000000100000f1d <main+25>: mov $0x6,%edi 0x0000000100000f22 <main+30>: callq 0x100000f2e <dyld_stub_do_foo> 0x0000000100000f27 <main+35>: mov $0x0,%eax 0x0000000100000f2c <main+40>: leaveq 0x0000000100000f2d <main+41>: retq End of assembler dump.
do_foo(int) の呼び出し部分は dyld_stub_do_foo というスタブ関数への呼出となっていることがわかる。引数は第一引数(ediレジスタ)に5を渡している。
スタブ関数は __symbol_stub1 セクションに有る。
(gdb) disas 0x100000f2e 0x100000f3a Dump of assembler code from 0x100000f2e to 0x100000f3a: 0x0000000100000f2e <dyld_stub_do_foo+0>: jmpq *0xdc(%rip) # 0x100001010 0x0000000100000f34 <dyld_stub_exit+0>: jmpq *0xde(%rip) # 0x100001018 End of assembler dump.
__symbol_stub1 セクションには exit のスタブ関数も有ることがわかる。とりあえず今は do_foo(int) のスタブ関数に注目。0xdc(%rip) つまり 0x100001010 に有る値を読み込みそのアドレスにjmpしている。0x100001010 は __la_symbol_ptr セクションに含まれるアドレスなのでこのコードは レジーシンボルポインタを経由して jmp していると言うことになる。
__la_symbol_ptr セクションは今の時点ではこう。
(gdb) x /2xg 0x0000000100001010 0x100001010: 0x0000000100000f4a 0x0000000100000f54
1個目のポインタが do_foo 用のレジーシンボルポインタで、2個目が exit 用だな。
ついでに __nl_symbol_ptr セクションは今の時点ではこう。
(gdb) x /2xg 0x0000000100001000 0x100001000: 0x0000000000000000 0x0000000000000000
何も入ってない・・・。
この辺りでプログラムを開始ししてみる。とりあえず C 言語の初期化関数である start にブレイクポイントを設定してみる。
(gdb) b start Breakpoint 1 at 0x100000ecd
もう一度__nl_symbol_ptrセクションを見てみる。
(gdb) x /2xg 0x0000000100001000 0x100001000: 0x00007fff82b84fa8 0x0000000000000000
なにか入っている。これは
(gdb) x /5i 0x00007fff82b84fa8 0x7fff82b84fa8: push %rbp 0x7fff82b84fa9 : mov %rsp,%rbp ・・・
からわかるように dyld_stub_binder のアドレス。もうひとつはまだ 0 のまま。
__la_symbol_ptr に目を戻そう。これらのポインタが指しているのは __stub_helper セクションだ。__stub_helper セクションはこう。
(gdb) disas 0x0000000100000f3a 0x0000000100000f5e Dump of assembler code from 0x100000f3a to 0x100000f5e: 0x0000000100000f3a < stub helpers+0>: lea 0xc7(%rip),%r11 # 0x100001008 0x0000000100000f41 < stub helpers+7>: push %r11 0x0000000100000f43 < stub helpers+9>: jmpq *0xb7(%rip) # 0x100001000 0x0000000100000f49 < stub helpers+15>: nop 0x0000000100000f4a < stub helpers+16>: pushq $0x0 0x0000000100000f4f < stub helpers+21>: jmpq 0x100000f3a < stub helpers> 0x0000000100000f54 < stub helpers+26>: pushq $0xe 0x0000000100000f59 < stub helpers+31>: jmpq 0x100000f3a < stub helpers> End of assembler dump.
スタブ関数からレジーポインタを通してここにjmpしてくる。do_foo の場合は 0x0 を, exit の場合は 0xe をスタックへ積んだ後、二つ目のノンレジーポインタのアドレス(0x100001008)をr11に設定し、さらにスタックにもプッシュして、一つ目のノンレジーポインタを通して dyld_stub_binder へ jmp している。
とりあえずこの jmp 直前まで実行してみる。
(gdb) b *0x0000000100000f43 Breakpoint 2 at 0x100000f43 (gdb) c Continuing. Breakpoint 2, 0x0000000100000f43 in stub helpers ()
この時、スタックの内容を確認してみると以下の様にstub helpersでプッシュされた 0x0 と 二つ目のノンレジーポインタのアドレスである 0x100001008 が格納されていることがわかる。
(gdb) x /1xg $rsp 0x7fff5fbff798: 0x0000000100001008 (gdb) x /1xg $rsp+8 0x7fff5fbff7a0: 0x0000000000000000
jmp 先である dyld_stub_binder をダンプ。(抜粋)
0x00007fff82b84fa8 <dyld_stub_binder+0>: push %rbp 0x00007fff82b84fa9 <dyld_stub_binder+1>: mov %rsp,%rbp 0x00007fff82b84fac <dyld_stub_binder+4>: sub $0xc0,%rsp (レジスタの保存) 0x00007fff82b85011 <dyld_stub_binder+105>: mov 0x8(%rbp),%rdi 0x00007fff82b85015 <dyld_stub_binder+109>: mov 0x10(%rbp),%rsi 0x00007fff82b85019 <dyld_stub_binder+113>: callq 0x7fff82a88c4f <_Z21_dyld_fast_stub_entryPvl> 0x00007fff82b8501e <dyld_stub_binder+118>: mov %rax,%r11 (レジスタの復元) 0x00007fff82b8507f <dyld_stub_binder+215>: add $0xc0,%rsp 0x00007fff82b85086 <dyld_stub_binder+222>: pop %rbp 0x00007fff82b85087 <dyld_stub_binder+223>: add $0x10,%rsp 0x00007fff82b8508b <dyld_stub_binder+227>: jmpq *%r11
dyld_stub_binder は /usr/lib/libSystem.B.dylib で定義されているコードであり、ソースは dyld_stub_binder.s というアセンブリコード。x86_64用のdyld_stub_binderラベルのコメントにはこのようにある。
/* * sp+4 lazy binding info offset * sp+0 address of ImageLoader cache */
・・・いやいや、sp+0 と sp+8 やろ。ま、それはいいとして、スタブヘルパでプッシュされていた 0x0 は レジーバインド情報のオフセット、 ノンレジーポインタの二つ目のアドレスは ImageLoader キャッシュのアドレス、ということらしい。・・・なんだそれ。とりあえず今回はは気にしないでブラックボックスだと思っておこう。
dyld_stub_binder の最後の方のコード、 dyld_stub_binder+215〜223 は スタックの状態を dyld_stub_do_foo 呼び出しの時と同じ状態に復元している。
dyld_stub_binder の最後の jmp でブレイクする。
(gdb) b *0x00007fff82b8508b Breakpoint 2 at 0x7fff82b8508b (gdb) c Continuing. Breakpoint 2, 0x00007fff82b8508b in dyld_stub_binder ()
スタック確認。
(gdb) x /1xg $rsp 0x7fff5fbff7a8: 0x0000000100000f1d
確かに戻りアドレスとして、最初の dyld_stub_do_foo を call した次の命令のアドレスが入っている。
(gdb) x /1i $r11 0x100003f54: push %rbp
ここで jmp しようとしている先は動的ライブラリの do_foo(int) で有り、dyld_stub_binder の中で解決されたことがわかる。
(gdb) x /1xg 0x100001010 0x100001010: 0x0000000100003f54
do_foo(int) 用のレジーポインタの値もスタブヘルパのアドレスから do_foo(int) のアドレスへと書き換えられている。これにより以降の dyld_stub_do_foo 呼び出しでは スタブヘルパや dyld_stub_binder を経由せずに直接 do_foo(int) へ処理が移ることになる。
(gdb) x /1xg 0x100001008 0x100001008: 0x00007fff5fc43c18
二つ目のノンレジーポインタ(ImageLoader キャッシュのアドレス) にも値が入っている。ノンレジーと言いながらもレジーっぽいタイミングで値が入るんだなー。一応ダンプしてみる。
(gdb) x /4xg 0x00007fff5fc43c18 0x7fff5fc43c18 <__dyld__ZL18initialPoolContent+2104>: 0x00007fff5fc3c8f0 0x00007fff5fbff8d8 0x7fff5fc43c28 <__dyld__ZL18initialPoolContent+2120>: 0x0000000000000000 0x0000000000000000
initialPoolContent というのは dyldNew.cpp で定義されているが、これが ImageLoader キャッシュ ??? やっぱり今回はこのあたりは謎のまま置いておこう。
動的ライブラリの観察その2
今回は動的ライブラリの関数呼び出しの様子を観察してみる。
動的ライブラリで定義されている関数のアドレスは動的ライブラリがメモリにロードされるまで決定しない。そのどこに有るかわからない関数をどのようにして呼び出すのかが見どころ。
動的ライブラリはこれ
$ cat libfoo1.c int do_foo(int i) { return i; }
$ gcc -dynamiclib -o libfoo1.dylib libfoo1.c
動的ライブラリを呼び出す方はこれ
$ cat hoge1.c int do_foo(int); int main(int argc, char **argv) { do_foo(5); do_foo(6); return 0; }
$ gcc -L./ -lfoo1 -o hoge1 hoge1.c
動的ライブラリの do_foo(int) を呼び出す様子を gdb で追跡してその概要をシーケンス図風にしたものがこれ。jmp, call はアセンブリ言語の分岐命令の様子を表し、indirect jmp と reference の組み合わせは メモリ上に格納されたアドレスを読み込み、さらにそのアドレスへと分岐する様子を表している。注意しなければならないのはアセンブリのcall,jmpは必ずしも呼び出し元に戻るとは限らないこと。今回も関数というよりはC言語の goto をイメージしながら見た方が良い。
- 1回目の do_foo(int) 呼び出し
libSystem.B.dylib の dyld_stub_binder(と、そこから呼び出される一連の処理)の中で do_foo(int) のアドレスが解決されて最終的に do_foo(int) に処理が移っている。この時スタックは 1:call() 実行直後の状態に復元されているので do_foo(int) の中で ret 命令を実行すると処理は 1:call() の次の命令から実行される。また、解決された do_foo(int) のアドレスは dyld_stub_binder の中でメモリ上のアドレス 0x100001010 の位置にも格納される。
- 2回目の do_foo(int) 呼び出し
2回目以降は 0x100001010 に do_foo(int) のアドレスが格納されているので余計な処理をせず直接 do_foo(int) へ処理を移すことができる。
dyld_stub_do_foo, stub helpers, __nl_symbol_ptr や __la_symbol_ptr は静的リンカが動的リンカのために生成したもの。hoge1.o にはそのようなセクションが含まれないことからも静的リンカが生成したとがわかる。
hoge1.o のセクション一覧(otoolの出力抜粋)
$ otool -l hoge1.o hoge1.o: Section sectname __text segname __TEXT Section sectname __eh_frame segname __TEXT
静的リンカが生成したセクションの内容は以下のようになっている(main実行開始時点)。gobjdumpよりもgdbでダンプした方が見やすかったのでgdbでのダンプを載せておく。
__symbol_stub1 セクションのダンプ
(gdb) disas 0x100000f2e 0x100000f3a Dump of assembler code from 0x100000f2e to 0x100000f3a: 0x0000000100000f2e <dyld_stub_do_foo+0>: jmpq *0xdc(%rip) # 0x100001010 0x0000000100000f34 <dyld_stub_exit+0>: jmpq *0xde(%rip) # 0x100001018 End of assembler dump.
__stub_helper セクションのダンプ
(gdb) disas 0x0000000100000f3a 0x0000000100000f5e Dump of assembler code from 0x100000f3a to 0x100000f5e: 0x0000000100000f3a < stub helpers+0>: lea 0xc7(%rip),%r11 # 0x100001008 0x0000000100000f41 < stub helpers+7>: push %r11 0x0000000100000f43 < stub helpers+9>: jmpq *0xb7(%rip) # 0x100001000 0x0000000100000f49 < stub helpers+15>: nop 0x0000000100000f4a < stub helpers+16>: pushq $0x0 0x0000000100000f4f < stub helpers+21>: jmpq 0x100000f3a < stub helpers> 0x0000000100000f54 < stub helpers+26>: pushq $0xe 0x0000000100000f59 < stub helpers+31>: jmpq 0x100000f3a < stub helpers> End of assembler dump.
__nl_symbol_ptr セクションのダンプ
(gdb) x /2xg 0x0000000100001000 0x100001000: 0x00007fff865fb4ac 0x0000000000000000
__la_symbol_ptr セクションのダンプ
(gdb) x /2xg 0x0000000100001010 0x100001010: 0x0000000100000f4a 0x0000000100000f54
ちなみに main(int, char**) はこう。
(gdb) disas main Dump of assembler code for function main: 0x0000000100000f04 <main+0>: push %rbp 0x0000000100000f05 <main+1>: mov %rsp,%rbp 0x0000000100000f08 <main+4>: sub $0x10,%rsp 0x0000000100000f0c <main+8>: mov %edi,-0x4(%rbp) 0x0000000100000f0f <main+11>: mov %rsi,-0x10(%rbp) 0x0000000100000f13 <main+15>: mov $0x5,%edi 0x0000000100000f18 <main+20>: callq 0x100000f2e <dyld_stub_do_foo> 0x0000000100000f1d <main+25>: mov $0x6,%edi 0x0000000100000f22 <main+30>: callq 0x100000f2e <dyld_stub_do_foo> 0x0000000100000f27 <main+35>: mov $0x0,%eax 0x0000000100000f2c <main+40>: leaveq 0x0000000100000f2d <main+41>: retq End of assembler dump.
ちなみに 0x0000000100001000 経由で関節参照されている 0x00007fff865fb4ac の冒頭はこう。
(gdb) x /5i 0x00007fff865fb4ac 0x7fff865fb4ac <dyld_stub_binder>: push %rbp 0x7fff865fb4ad <dyld_stub_binder+1>: mov %rsp,%rbp 0x7fff865fb4b0 <dyld_stub_binder+4>: sub $0xc0,%rsp 0x7fff865fb4b7 <dyld_stub_binder+11>: mov %rdi,(%rsp) 0x7fff865fb4bb <dyld_stub_binder+15>: mov %rsi,0x8(%rsp)
このダンプ結果からもdo_foo(int)の初回呼び出しでは上のシーケンス図の通りに処理が移っていくことがわかる。
main+20のcall → dyld_stub_do_foo+0 → dyld_stub_do_foo+0 の jmp → 0x100001010 の値を参照 → stub helpers+16 → stub helpers+21 の jmp → stub helpers+0 → stub helpers+9 の jmp → 0x0000000100001000 の値を参照 → dyld_stub_binder
Mac-O Programing Topics: Indirect Addressingにインダイレクトアドレッシング(間接アドレッシング)と言うものの説明がある。
関節アドレッシングとは、他のファイルで定義されたシンボルをそのファイルの構造を知らなくても参照できるようなコードを生成する技術の名前である。これにより、シンボルを定義しているファイルはそれを参照しているファイルとは独立して変更することが可能となる。間接アドレッシングを利用すれば、動的リンカがリンク時に変更しなければならない箇所が減り、結果としてコードを共有しやすくなったり、パフォーマンスが改善されたりする。
あるファイルが別のファイルで定義されているデータを参照する場合、「シンボルリファレンス」が作成される。シンボルリファレンスとはどのシンボルがどのファイルからインポートされたかを特定するためのものである。シンボルリファレンスには「ノンレジー(nonlazy)」と 「レジー(lazy)」の二種類がある。
- ノンレジーシンボルリファレンスはモジュールのロード時に動的リンカによって解決(シンボルの定義に結合)される。ノンレジーシンボルリファレンスは基本的にはシンボルへのポインタであり、データや関数へのノンレジーポインタはコンパイラが生成する。
- レジーシンボルリファレンスはロード時ではなく最初に使用されるときに動的リンカによって解決される。以降の使用ではシンボルの定義に直接ジャンプする。レジーシンボルリファレンスはシンボルポインタとシンボルスタブからなる。シンボルスタブとはシンボルポインタをデリファレンスしてジャンプする小さなコードである。レジーシンボルリファレンスは、他のファイルで定義されている関数への呼び出しが見つかった場合にコンパイラが作成する。
x86_64では静的リンカが、動的リンカに必要な関節シンボルテーブルはもちろん、全てのスタブ関数、スタブヘルパ関数、レジー&ノンレジーポインタの生成の責任を負う。
__symbol_stub1, __stub_helper, __nl_symbol_ptr, __la_symbol_ptr 辺りが正にこれか。
まとめ
- レジーシンボルリファレンスを使って、動的ライブラリの関数に直接飛ぶのかそのアドレス解決のためのコードに飛ぶのかを分岐させている
- 一度動的ライブラリの関数のアドレスが解決されると2回目以降の呼び出しでは解決のオーバーヘッドは無い
動的ライブラリの観察その1
今回からは動的ライブラリが利用される様子を観察してみる。
ライブラリとは
リンク可能なオブジェクトファイルの集まり。ライブラリには静的ライブラリと動的ライブラリが有る。
静的ライブラリ
- 実行形式ファイルには必要なコードが全てコピーされる
- 実行形式ファイルのサイズは大きくなり起動時間は長い
- 実行形式ファイルの占有メモリ容量は大きい
- ライブラリに修正が加えられても再度コンパイルしなければ実行形式ファイルには反映されない
- 他のファイルに依存すること無く実行形式ファイル単体で実行可能
動的ライブラリ
- 実行形式ファイルにはどのライブラリを利用するかという情報だけが記録される
- 必要なライブラリは起動時にメモリに読み込まれる
- 実行時に動的に読み込むことも可能
- 実行形式ファイルのサイズは小さく起動時間は早い
- 実行形式ファイルの占有メモリ容量は小さい。
- ライブラリに修正が加えられれば実行形式は変更しなくても反映される
- 必要なライブラリが存在しなければ実行できない
ちなみに、Mac OS X ではユーザーバイナリの静的リンクはサポートしていない。(Static linking of user binaries on Mac OS X)
実際の動的ライブラリで観察
まずはプロセスのアドレス空間のどの辺りに読み込まれるのかを見てみる。
単純な動的ラブラリを作る。
$ cat libhoge4.c int do_hoge(int i) { return i; }
$ gcc --save-temps -dynamiclib -o libhoge4.dylib libhoge4.c $ file libhoge4.dylib libhoge4.dylib: Mach-O 64-bit dynamically linked shared library x86_64
動的ライブラリを利用するコード
$ cat hoge4.c #include <unistd.h> #include <stdio.h> int do_hoge(int); int main(int argc, char **argv) { do_hoge(5); do_hoge(6); puts("asdf\n"); }
$ gcc --save-temps -L./ -lhoge4 -o hoge4 hoge4.c $ otool -L hoge4 hoge4: libhoge4.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.0.0)
otoolコマンドでリンクされている動的ライブラリの一覧を表示することができる。hoge4が先程作った動的ライブラリとリンクされていることがわかる。
gdbで実行。メイン関数から戻る辺りで止めておく。
$ gdb hoge4 (gdb) disas main Dump of assembler code for function main: 0x0000000100000ee8: push %rbp 0x0000000100000ee9 : mov %rsp,%rbp 0x0000000100000eec : sub $0x10,%rsp 0x0000000100000ef0 : mov %edi,-0x4(%rbp) 0x0000000100000ef3 : mov %rsi,-0x10(%rbp) 0x0000000100000ef7 : mov $0x5,%edi 0x0000000100000efc : callq 0x100000f1a 0x0000000100000f01 : mov $0x6,%edi 0x0000000100000f06 : callq 0x100000f1a 0x0000000100000f0b : lea 0x48(%rip),%rdi # 0x100000f5a 0x0000000100000f12 : callq 0x100000f26 0x0000000100000f17 : leaveq 0x0000000100000f18 : retq End of assembler dump. (gdb) b *0x0000000100000f18 Breakpoint 1 at 0x100000f18 (gdb) run Starting program: hoge4 Reading symbols for shared libraries ++. done asdf Breakpoint 1, 0x0000000100000f18 in main () (gdb)
vmmapコマンドで仮想アドレス空間のレイアウトを表示する。これにはどのファイルの内容がどのアドレス範囲に読み込まれているかや、そのアドレス範囲の属性(読込可否、書込実可否、実行可否、共有モードetc)が含まれている。ちなみに、Linuxでも cat /proc/<プロセスID>/maps とすればよく似た情報を得ることができる。
$ ps | grep hoge4 89976 s004 S+ 0:00.07 /usr/libexec/gdb/gdb-i386-apple-darwin hoge4 89987 s004 SX 0:00.01 hoge4 89998 s005 S+ 0:00.00 grep hoge4 $ vmmap -interleaved 89987 ← hoge4 のプロセスID Virtual Memory Map of process 89987 (hoge4) Output report format: 2.2 -- 64-bit process ==== regions for process 89987 (non-writable and writable regions are interleaved) __TEXT 0000000100000000-0000000100001000 [ 4K] r-x/rwx SM=COW hoge4 __DATA 0000000100001000-0000000100002000 [ 4K] rw-/rwx SM=PRV hoge4 __LINKEDIT 0000000100002000-0000000100003000 [ 4K] r--/rwx SM=COW hoge4 __TEXT 0000000100003000-0000000100004000 [ 4K] r-x/rwx SM=COW libhoge4.dylib __LINKEDIT 0000000100004000-0000000100005000 [ 4K] r--/rwx SM=COW libhoge4.dylib STACK GUARD 0000000100005000-0000000100006000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100006000-0000000100007000 [ 4K] rw-/rwx SM=COW STACK GUARD 0000000100007000-0000000100009000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100009000-0000000100014000 [ 44K] rw-/rwx SM=COW STACK GUARD 0000000100014000-0000000100016000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100016000-0000000100021000 [ 44K] rw-/rwx SM=COW STACK GUARD 0000000100021000-0000000100022000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100022000-0000000100023000 [ 4K] r--/rwx SM=COW MALLOC_TINY 0000000100100000-0000000100200000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x100006000 MALLOC_SMALL 0000000100800000-0000000101000000 [ 8192K] rw-/rwx SM=COW DefaultMallocZone_0x100006000 STACK GUARD 00007fff5bc00000-00007fff5f400000 [ 56.0M] ---/rwx SM=NUL Stack 00007fff5f400000-00007fff5fbfd000 [ 8180K] rw-/rwx SM=ZER Stack 00007fff5fbfd000-00007fff5fbfe000 [ 4K] rw-/rwx SM=PRV Stack 00007fff5fbfe000-00007fff5fbff000 [ 4K] rw-/rwx SM=ZER Stack 00007fff5fbff000-00007fff5fc00000 [ 4K] rw-/rwx SM=COW thread 0 __TEXT 00007fff5fc00000-00007fff5fc0a000 [ 40K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc0a000-00007fff5fc0b000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc0b000-00007fff5fc3c000 [ 196K] r-x/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc3c000-00007fff5fc41000 [ 20K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc41000-00007fff5fc43000 [ 8K] rw-/rwx SM=ZER /usr/lib/dyld __DATA 00007fff5fc43000-00007fff5fc44000 [ 4K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc44000-00007fff5fc7b000 [ 220K] rw-/rwx SM=ZER /usr/lib/dyld __LINKEDIT 00007fff5fc7b000-00007fff5fc8f000 [ 80K] r--/rwx SM=COW /usr/lib/dyld __DATA 00007fff70b60000-00007fff70b83000 [ 140K] rw-/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff864f3000-00007fff866b2000 [ 1788K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff86780000-00007fff86785000 [ 20K] r-x/r-x SM=COW /usr/lib/system/libmathCommon.A.dylib __LINKEDIT 00007fff86f3b000-00007fff88439000 [ 21.0M] r--/r-- SM=COW /usr/lib/system/libmathCommon.A.dylib ==== Legend SM=sharing mode: COW=copy_on_write PRV=private NUL=empty ALI=aliased SHM=shared ZER=zero_filled S/A=shared_alias
libhoge4.dylib が hoge4 のすぐ上にロードされている。libSystem.B.dylib はずいぶん上の端にロードされている。libmathCommon.A.dylibというのは・・・・
$ otool -L /usr/lib/libSystem.B.dylib /usr/lib/libSystem.B.dylib: /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.0.0) /usr/lib/system/libmathCommon.A.dylib (compatibility version 1.0.0, current version 315.0.0)
libSystem.B.dylibが依存しているライブラリか。dyldというのはダイナミックローダー。これが実行形式ファイルや各動的ライブラリをロードしてくれる。
それにしても上の図とはライブラリの配置がずいぶん違っているな。"共有メモリ"という仕組みの関係でこう成っているのかもしれない。そのうち調べよう。
上の結果にさらに otool -l で得たセクションの情報も合わせてみるとこうなる。
__TEXT 0000000100000000-0000000100001000 [ 4K] r-x/rwx SM=COW hoge4 sectname __text segname __TEXT addr 0x0000000100000eac size 0x000000000000006d sectname __symbol_stub1 segname __TEXT addr 0x0000000100000f1a size 0x0000000000000012 sectname __stub_helper segname __TEXT addr 0x0000000100000f2c size 0x000000000000002e sectname __cstring segname __TEXT addr 0x0000000100000f5a size 0x0000000000000006 sectname __unwind_info segname __TEXT addr 0x0000000100000f60 size 0x0000000000000054 sectname __eh_frame segname __TEXT addr 0x0000000100000fb8 size 0x0000000000000048 __DATA 0000000100001000-0000000100002000 [ 4K] rw-/rwx SM=COW hoge4 sectname __nl_symbol_ptr segname __DATA addr 0x0000000100001000 size 0x0000000000000010 sectname __la_symbol_ptr segname __DATA addr 0x0000000100001010 size 0x0000000000000018 sectname __program_vars segname __DATA addr 0x0000000100001040 size 0x0000000000000028 sectname __data segname __DATA addr 0x0000000100001068 size 0x0000000000000020 __LINKEDIT 0000000100002000-0000000100003000 [ 4K] r--/rwx SM=COW hoge4 __TEXT 0000000100003000-0000000100004000 [ 4K] r-x/rwx SM=COW libhoge4.dylib __LINKEDIT 0000000100004000-0000000100005000 [ 4K] r--/rwx SM=COW libhoge4.dylib STACK GUARD 0000000100005000-0000000100006000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100006000-0000000100007000 [ 4K] rw-/rwx SM=COW STACK GUARD 0000000100007000-0000000100009000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100009000-0000000100014000 [ 44K] rw-/rwx SM=COW STACK GUARD 0000000100014000-0000000100016000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100016000-0000000100021000 [ 44K] rw-/rwx SM=PRV STACK GUARD 0000000100021000-0000000100022000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100022000-0000000100023000 [ 4K] r--/rwx SM=COW MALLOC_TINY 0000000100100000-0000000100200000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x100006000 STACK GUARD 00007fff5bc00000-00007fff5f400000 [ 56.0M] ---/rwx SM=NUL Stack 00007fff5f400000-00007fff5fbfd000 [ 8180K] rw-/rwx SM=ZER Stack 00007fff5fbfd000-00007fff5fbfe000 [ 4K] rw-/rwx SM=PRV Stack 00007fff5fbfe000-00007fff5fbff000 [ 4K] rw-/rwx SM=ZER Stack 00007fff5fbff000-00007fff5fc00000 [ 4K] rw-/rwx SM=COW thread 0 __TEXT 00007fff5fc00000-00007fff5fc0a000 [ 40K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc0a000-00007fff5fc0b000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc0b000-00007fff5fc16000 [ 44K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc16000-00007fff5fc17000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc17000-00007fff5fc3c000 [ 148K] r-x/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc3c000-00007fff5fc41000 [ 20K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc41000-00007fff5fc43000 [ 8K] rw-/rwx SM=ZER /usr/lib/dyld __DATA 00007fff5fc43000-00007fff5fc44000 [ 4K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc44000-00007fff5fc7b000 [ 220K] rw-/rwx SM=ZER /usr/lib/dyld __LINKEDIT 00007fff5fc7b000-00007fff5fc8f000 [ 80K] r--/rwx SM=COW /usr/lib/dyld __DATA 00007fff70b60000-00007fff70ba0000 [ 256K] rw-/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff864f3000-00007fff865fb000 [ 1056K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff865fb000-00007fff865fc000 [ 4K] r-x/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff865fb000-00007fff86780000 [ 1556K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff86780000-00007fff86785000 [ 20K] r-x/r-x SM=COW /usr/lib/system/libmathCommon.A.dylib __TEXT 00007fff86785000-00007fff867ba000 [ 212K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __LINKEDIT 00007fff86f3b000-00007fff88439000 [ 21.0M] r--/r-- SM=COW /usr/lib/system/libmathCommon.A.dylib
まとめ
- 動的ライブラリを利用する実行形式ファイルが実行されている時の具体的なメモリレイアウトを確認した