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 で処理する実装
  • 最後のほうにクラス図(抜粋)載せてます

通信不能時の状態

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全てのスレッドで起きるとリンクをタップしても通信しないと言う症状が発生する。

図示してみる

  • 関与しているクラスのクラス図(抜粋)


  • ロードが正常に完了する場合のシーケンス図(抜粋)


  • httpxスレッドが10分間waitになってしまう場合のシーケンス図

Dropbox API を curl で叩いてみる

curlDropboxREST 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"}

パラメータを敢えて間違えるとエラーレスポンスも体験できる。

シェルスクリプトにしてみた

んー。シェルでやるもんじゃあないなぁ(笑)
連想配列のないbashJSONは相性が悪い。


dropbox_api.sh

#!/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以外も多分同様)

大まかな流れ

  1. Dropboxで通常のアカウント作成
  2. ディベロッパー登録
  3. 製作するアプリの情報を登録(アプリ名、説明など)
    • OAuth の Consumer Key と Consumer Secret が発行される
    • このキーにはまずは開発版のAPIアクセス権が与えられる
    • 開発版なのでアプリ製作者のアカウントでしかAPIを利用できない
  4. SDKのダウンロード、開発、テスト
  5. アプリの公開申請
    • キーのAPIアクセス権をプロダクト版に変更するための申請
    • これが通ると全てのアカウントからAPIにアクセスが可能となる

ディベロッパー登録

通常の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 とは一体どういうデータなのか・・・。これを知るためにはローディングの仕組を理解する必要があるのかなぁ。もう少しで、動的ライブラリの関数がバインドされる瞬間を見れそうな気がするのだが・・・とりあえず今日はここまで。

まとめ

  • dyld の APIは文字列で索引を引いて呼び出す
  • 実行形式やライブラリがロードされているとき、ロードしたアドレス範囲ごとにImageLoaderのインスタンスが有る(と思われる)
  • 関数のアドレスが解決されるのは ImageLoaderMachOCompressed の中(と思われる)

動的ライブラリの観察その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 キャッシュ ??? やっぱり今回はこのあたりは謎のまま置いておこう。

まとめ

  • ジーシンボルポインタが動的ライブラリによってレジーバインドされていた
  • dyld_stub_binder 以降の処理はまだブラックボックスのまま。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

まとめ

  • 動的ライブラリを利用する実行形式ファイルが実行されている時の具体的なメモリレイアウトを確認した