1 組込みエンジニアのための Linux 入門 ダイナミックリンク編 (2) 2007.7.12 株式会社アプリックス 小林哲之.

Slides:



Advertisements
Similar presentations
1 B10 CPU を作る 1 日目 解説 TA 高田正法
Advertisements

C 言語講座 第 7 回 ポインター. メモリとアドレス(ポインターの前 に) コンピュータのメモリには 1 バイトずつ 0 番地、 1 番地、 2 番地・・・というように 住所が割り当てられている この住所をアドレスという。 メモリはデータをしまうもので それを引き出すためには メモリに番号(アドレス)を振っておけばよいな.
2.5 プログラムの構成要素 (1)文字セット ① ASCII ( American Standard Code for Interchange ) JIS コードと同じ ② EBCDIC ( Extended Binary Coded Decimal for Information Code ) 1.
アルゴリズムとデータ構造 第2回 線形リスト(復習).
プログラミング演習II 2004年11月 30日(第6回) 理学部数学科・木村巌.
組込みエンジニアのためのLinux入門 ダイナミックリンク編(3)
コンパイラの解析 (1) プログラムのリンクと実行.
Unix生活 Vol.1
自社製ミドルウエアをDalvikと連携させることが可能になる
情報基礎演習B 後半第5回 担当 岩村 TA 谷本君.
1.1 C/C++言語 Hello.ccを作りコンパイルしてa.outを作り出し実行する
データ構造とアルゴリズム 第10回 mallocとfree
Step-by-Step Guide on How to Start ALICE Analysis
システムプログラミング 第5回 情報工学科 篠埜 功 ヒアドキュメント レポート課題 main関数の引数 usageメッセージ
OSとコマンド OS:コンピュータを使うための基本プログラム コマンド:OS上で使用できる命令 OS本体であるカーネルの内部コマンド
App. A アセンブラ、リンカ、 SPIMシミュレータ
侵入検知システム(IDS) 停止 IDS サーバへの不正アクセスが増加している
情報処理Ⅱ 2005年12月9日(金).
第8回 プログラミングⅡ 第8回
構造体.
プログラミング演習Ⅰ 課題2 10進数と2進数 2回目.
Linuxカーネルについて 2014/01.
Web 2.0 Conference 2005 実行時自己書き換え佳境 首藤 一幸 話の流れのスライドを加える?
ファイル操作と文字列の利用.
コンパイラの解析 (2) GCJのデータ構造 - 1.
情報工学科 3年生対象 専門科目 システムプログラミング 第5回、第6回 ヒアドキュメント レポート課題 情報工学科 篠埜 功.
型付きアセンブリ言語を用いた安全なカーネル拡張
プログラミング論 関数ポインタ と 応用(qsort)
プログラミング論 ファイル入出力
東京工科大学 コンピュータサイエンス学部 亀田弘之
コンパイラの解析 (3) クラスとインスタンスの初期化.
セキュリティ(3) 05A2013 大川内 斉.
関数の定義.
コンピュータ系実験Ⅲ 「ワンチップマイコンの応用」 第1週目 アセンブリ言語講座
プログラミング演習I 2003年6月25日(第10回) 木村巌.
第10章 これはかなり大変な事項!! ~ポインタ~
第7回 プログラミングⅡ 第7回
TA 高田正法 B10 CPUを作る 3日目 SPIMの改造 TA 高田正法
プログラミング言語論 第五回 理工学部 情報システム工学科 新田直也.
アルゴリズムとデータ構造 補足資料5-1 「メモリとポインタ」
プログラミング論 ファイル入出力
デジタル画像とC言語.
C#言語ソースプログラムの原型 C言語 C#言語 Hello World! Hello Students! オマジナイ! 適当なクラス名
コンパイラ資料 実行時環境.
オブジェクト指向プログラミングと開発環境
メモリとメモリアドレス, ポインタ変数,関数へのポインタ渡し
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
プログラミング言語論 第六回 理工学部 情報システム工学科 新田直也.
プログラミング演習I 2003年4月30日(第3回) 木村巌.
参照されないリテラル 長谷川啓
実装について 前田俊行.
ポインタとポインタを用いた関数定義.
プログラミング論 ポインタ
11.1 標準ライブラリ関数 11.2 関数呼び出しのオーバーヘッド 11.3 大域変数 11.4 プロトタイプ宣言 11.5 関数引数
アルゴリズムとデータ構造1 2009年6月15日
第5回 プログラミングⅡ 第5回
オブジェクト指向言語論 第五回 知能情報学部 新田直也.
ドキュメントジェネレータ 詳細仕様 長谷川啓
モジュール分割.
情報工学科 3年生対象 専門科目 システムプログラミング 第3回 makeコマンド 動的リンクライブラリ 情報工学科 篠埜 功.
アルゴリズムとデータ構造 2010年6月17日
情報工学科 3年生対象 専門科目 システムプログラミング 第3回 makeコマンド 動的リンクライブラリ 情報工学科 篠埜 功.
フレンド関数とフレンド演算子.
プログラミング演習I 2003年6月11日(第9回) 木村巌.
プログラミング演習II 2004年11月 16日(第5回) 理学部数学科・木村巌.
情報処理Ⅱ 小テスト 2005年2月1日(火).
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
6.5 最終コード生成 (1)コードの形式 ①絶対2進コード(AB : absolute binary) 命令後のオペランドが絶対番地指定。
計算機プログラミングI 第2回 2002年10月17日(木) 履習登録 複習 ライブラリの利用 (2.6-7) 式・値・代入 (2.6-8)
プログラミング 2 静的変数.
Presentation transcript:

1 組込みエンジニアのための Linux 入門 ダイナミックリンク編 (2) 株式会社アプリックス 小林哲之

2 このスライドの対象とする方 今までずっと組込み機器のプロジェ クトに携わってきて最近は OS に Linux を使っている方々

3 このスライドの目的 Linux で使用されているダイナミック リンクの仕組みを理解し現在のプロ ジェクトに役立てる。 – 仕組みを知らなくてもプログラムは動 くが、トラブルに対処したり性能を引 き出すためには仕組みの理解が重要。

4 今日のお題 ダイナミックリンクライブラリの関数呼 び出しの実際 – 裏でリンカ、ローダはどんなことをしてくれ ているのか? ダイナミックリンクライブラリを作って 動かしてみる –PIC, シンボルの visibility 残念ながら prelink は次回以降で。

5 ダイナミックリンクライブラ リの 関数呼び出しの実際

6 ダイナミックリンクライブラリ main() program libraries リンク時に配置されるアドレスが決まっている - 1 プロセスにひとつしかないので固定アドレスでよい。 リンク時に配置されるアドレスが決まっていない。 - 1 プロセスに複数のライブラリが使われる。 - ライブラリ同士が重ならないように配置しなければ ならない。 ロード時に配置されるアドレスが決まる。

7 実際のコードを見てみる main hello2 puts programlibc hello1 #include void hello2(char *msg) { puts(msg);} void hello1(char *msg) { hello2(msg);} int main() { hello1("Hello, world"); return 0;}

8 ダイナミックリンクライブラリの 関数呼び出し hello1hello2 puts programlibc ? リンク時にはアドレス が決まっていない。 どうやって呼び出す? main

9 ダイナミックリンクライブラリの 関数呼び出し hello1hello2 puts programlibc GOT リンカが生成 ジャンプテーブル経由でライブラリの関数を呼び出す。 ローダが 値を入れる PLT: Procedure Linkage Table GOT: Global Offset Table main

10 hello1 から hello2 の呼び出し (i386) : :55 push %ebp :89 e5 mov %esp,%ebp :83 ec 08 sub $0x8,%esp a:8b mov 0x8(%ebp),%eax d: mov %eax,(%esp) :e8 2f ff ff ff call :c9 leave :c3 ret : :55 push %ebp :89 e5 mov %esp,%ebp a:83 ec 08 sub $0x8,%esp d:8b mov 0x8(%ebp),%eax : mov %eax,(%esp) :e8 dc ff ff ff call :c9 leave :c3 ret

: :ff jmp *0x a: push $0x f:e9 c0 ff ff ff jmp hello2 から puts の呼び出し (i386) : :e8 2f ff ff ff call : libc の puts.got 間接ジャンプ

12 hello1 から hello2 の呼び出し (arm) c : 837c:e52de004 strlr, [sp, #-4]! 8380:e24dd004 subsp, sp, #4; 0x4 8384:ebffffc4 bl829c 8388:e28dd004 addsp, sp, #4; 0x4 838c:e8bd8000 ldmiasp!, {pc} : 8390:e52de004 strlr, [sp, #-4]! 8394:e24dd004 subsp, sp, #4; 0x4 8398:ebfffff7 bl837c 839c:e28dd004 addsp, sp, #4; 0x4 83a0:e8bd8000 ldmiasp!, {pc}

c : :ebffffc4 bl829c 829c:e28fc600 addip, pc, #0; 0x0 82a0:e28cca08 addip, ip, #32768; 0x a4:e5bcf2bc ldrpc, [ip, #700]! hello2 から puts の呼び出し (arm) 10560: libc の puts.got ip = (0x829c + 8) + 0x = 0x10560 pc = *(ip)

14 Lazy binding ( 遅延バインディング ) ローダが GOT の値の設定するが、その方法に2 つの選択肢 – 起動時に全て設定 ( 環境変数 LD_BIND_NOW=1 としたときの動作 ) – 実行時に最初に使用された時に設定 (= lazy binding) ( デフォルトの動作 ) 関数の参照の初期値にはローダ内にある参照を解決するため の 関数 ( = __dl_runtime_resolve) がセットされている。 最初に使用したときにその関数が呼ばれて、 GOT に 解決されたアドレスが書き込まれる。 変数の参照は起動時に全て解決される。

: :ff jmp *0x a: push $0x f:e9 c0 ff ff ff jmp Lazy binding の実際 (i386) : :e8 2f ff ff ff call :.got 初期値として 0x a( = + 6) が入っている plt を経由してローダの __dl_runtime_resolve へ puts の参照を解決して ここを書き換え puts にジャンプ を表す識別子

: 8288:e52de004 strlr, [sp, #-4]! 828c:e59fe004 ldrlr, [pc, #4]; :e08fe00e addlr, pc, lr 8294:e5bef008 ldrpc, [lr, #8]! 8298:000082bc.word0x000082bc c : :ebffffc4 bl829c 829c:e28fc600 addip, pc, #0; 0x0 82a0:e28cca08 addip, ip, #32768; 0x a4:e5bcf2bc ldrpc, [ip, #700]! Lazy binding の実際 (arm) 1055c: 10560:.got ip = (0x829c + 8) + 0x = 0x10560 pc = *(ip) lr = 0x000082bc + (0x ) + 8 = 0x1055c pc = *(lr) ローダの __dl_runtime_resolve

17 ダイナミックリンクのためのサイ ズの増加 関数のエントリひとつごとに (arm) –plt スタブ 3 命令 12 バイト –GOT 1 エントリ 4 バイト – シンボルの文字列 平均 10 バイトくらい? – その他... ? ダイナミックリンクライブラリの利点に比べれば 細かいことだが、把握しておいたほうがいいかも

18 PLT, GOT をどうしてもどうしても 節約したい場合 ライブラリ関数へのポインタを取得して、 それ経由で呼び出せばよい。 #include int (*puts_addr)(const char*); void hello1(char* msg) { puts_addr(msg); } int main() { puts_addr = puts; hello1("hello, world"); return 0; } ただしこう書いても puts_addr に得られるアドレスは libc の puts でなくて なので意味が無い。

19 dlsym を利用してアドレスを得 る $ cat p2.c #include int (*puts_addr)(const char*); void hello1(char* msg) { puts_addr(msg); } int main() { puts_addr = dlsym(RTLD_DEFAULT, "puts"); hello1("hello, world"); return 0; } $ cc -D_GNU_SOURCE p2.c -ldl $ libc のように必ずロードされているとわかっているライブラリでは dlopen しなくても、 RTLD_DEFAULT という定義済みのハンドラが 使用できる。 (dlclose を心配しなくても済む。 )

20 自作ライブラリの場合なら 関数ポインタ ( のストラクチャ ) へのポイン タを返す関数を用意する。 int func_a() int func_b() int func_c() static int func_a() static int func_b() static int func_c() FUNCS* get_funcs() typedef struct { int (*func_a)(); int (*func_b)(); int (*func_c)(); } FUNCS; APIs single API

21 まとめ ダイナミックリンクライブラリの関数呼 び出しと プログラム内の関数呼び出しでは、 C の ソース上ではほとんど変わらないが、実 際の動作ではダイナミックリンクライブ ラリの呼び出しは複雑。 複雑な仕組みはリンカとローダが隠蔽し てくれている。

22 ダイナミックリンクライブラ リの 作って動かしてみる

23 ダイナミックリンクライブラリの 作り方 リンク時に –shared をつける。 [ 必須 ] コンパイル時に –fpic をつけて、 Position Independent Code にする。 [ 推奨 ] ( 後述 ) –-fpic と -fPIC の違いはドキュメント参照 ライブラリの名前は lib ~.so とする。 gcc4.0 以降の場合は 適切な visibility を指定する。 ( 後述 )

24 簡単な実例 $ make cc -fpic -c -o hello1.o hello1.c cc -fpic -c -o hello2.o hello2.c cc -shared -o libhello.so hello1.o hello2.o -lc $ file libhello.so libhello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped $

25 ダイナミックリンクライブラリの インストールの方法 暫定的に使う場合 – 環境変数 LD_LIBRARY_PATH に設定する。 – コロン ( : ) で区切って複数のライブラリを指定できる。 恒久的なインストール –/lib または /usr/lib に置くか、 /etc/ld.so.conf に 登録されているディレクトリに置くか、ライブラリ の あるディレクトリを /etc/ld.so.conf に登録する。 – その後、ルート権限で /sbin/ldconfig コマンドを実行 する。 – ライブラリの情報が /etc/ld.so.cache にキャッシュさ れる ので、 LD_LIBRARY_PATH を使うより起動時間が短 縮できる。

26 PIC (Position Independent Code) 絶対アドレスによる参照の代わりに、プログラ ムカウンタからの相対アドレスを使用する。 ロードするアドレスが変わってもコードを書き 換える必要が無い。 –i386 ではプログラムカウンタ相対のアドレッシング モードがないので汚いコードになる。 PC を取得するために call 命令を使ってスタックに PC を書き 出させるなど。 – 組込み系で使用するプロセッサでも数命令多く必要 な場合がある。 コードサイズが少し大きくなり、その分だけ実 行時間も増える。

27 PIC/ 非 PIC のコードの比較 extern int gvar; static int svar; void setGvar(int var) { gvar = var; } void setSvar(int var) { svar = var; } グローバル変数、スタティック変数への書き込み

28 グローバル変数への書き込み (arm) setGvar: ldr r3,.L3.LPIC0: add r3, pc, r3 ldr r2,.L3+4 ldr r3, [r3, r2] str r0, [r3, #0] bx lr.L4:.align 2.L3:.word _GLOBAL_OFFSET_TABLE_ -(.LPIC0+8).word gvar(GOT) PIC setGvar: ldr r3,.L3 str r0, [r3, #0] bx lr.L4:.align 2.L3:.word gvar Non PIC 3 命令 + 1 ワード増加 ( 計 16 バイト ) 変数 gvar の絶対アドレス GLOBAL_OFFSET_TABLE gvar(GOT) gvar

29 static 変数への書き込み (arm) setSvar: ldr r3,.L7.LPIC1: add r3, pc, r3 ldr r2,.L7+4 str r0, [r3, r2] bx lr.L8:.align 2.L7:.word _GLOBAL_OFFSET_TABLE_ -(.LPIC1+8).word.LANCHOR0(GOTOFF) setSvar: ldr r3,.L7 str r0, [r3, #0] bx lr.L8:.align 2.L7:.word.LANCHOR0 Non PICPIC 2 命令 + 1 ワード増加 ( 計 12 バイト ) グローバル変数のときより 1 命令少ないのは GOT にオフセットでなく変数そのものが格納されるため static 変数 svar の絶対アドレス GLOBAL_OFFSET_TABLE gvar(GOT) gvar

30 グローバル関数の呼び出し (arm) call_gfunc: str lr, [sp, #-4]! sub sp, sp, #4 bl gfunc add sp, sp, #4 ldmfd sp!, {pc} call_gfunc: str lr, [sp, #-4]! sub sp, sp, #4 bl gfunc(PLT) add sp, sp, #4 ldmfd sp!, {pc} extern int gfunc(); void call_gfunc() { gfunc(); } Non PIC PIC gfunc の直接呼出し ( ローダが gfunc のアドレスが 確定した後にこの bl 命令のオフセットを 書き換える。 ) PLT スタブ経由

31 Non PIC のコードの問題点 setGvar: ldr r3,.L3 str r0, [r3, #0] bx lr.L4:.align 2.L3:.word gvar Non PIC 変数 gvar の絶対アドレス ダイナミックリンクライブラリの場合は ロードした後に絶対アドレスが決まる。 ローダが解決した値をここに書き込む ( ローダの負荷が大きく起動時間増大 ) コード領域に書き込みが発生する コード領域のそのページは 共有できない (dirty で private なページになる。) 物理メモリ使用量増大 PIC ならば実行され ないコード領域は 物理メモリにロード されない。

32 シンボルの visibility デフォルトでは全ての static でない関数、変数は ライブラリの外部からの参照が可能になってい る。 → そのため、 PLT スタブ経由でのアクセスに なっている → オーバーヘッド多め デフォルトの設定を「ライブラリ外から参照不 可」にしてインタフェース関数のみを明示的に 外部から参照を許可するほうがよい。 シンボルの visibility の設定は gcc 4.0 以降で可能。

33 libhello hellohello1 puts libhellolibc hello2 $ cat hello1.c void hello1() { hello2();} void hello() { hello1();} $ cat hello2.c #include void hello2(){ puts("Hello, world\n");} $ make cc -fpic -c -o hello1.o hello1.c cc -fpic -c -o hello2.o hello2.c cc -shared -o libhello.so hello1.o hello2.o -lc $

34 実際には hellohello1 puts libhellolibc hello2 hello1, hello2 の呼び出しはライブラリ内に閉じているのにも かかわらず PLT スタブ経由の呼び出しになってしまう。 (static 関数にすれば直接コールされるが同じソースファイルにある必要がある... )

35 余談 hellohello1 puts libhello1 libc hello2 hello1 同一の関数名 hello1 を持つライブラリ libhello1 を作って 環境変数 LD_PRELOAD を使って検索パスの前に置くと この図のように、 libhello を変更せずに関数 hello1 を置き換える ことができる。 “BINARY HACKS” hack #60 LD_PRELOAD で共有ライブラリを差し替える libhello

36 visibility を設定すると hellohello1 puts libhellolibc hello2 hello だけ公開し、それ以外を非公開にする $cat hello1.c #define EXPORT __attribute__((visibility ("default"))) void hello1() { hello2();} EXPORT void hello(){ hello1();} $ make cc -fpic -fvisibility=hidden -c -o hello1.o hello1.c cc -fpic -fvisibility=hidden -c -o hello2.o hello2.c cc -shared -o libhello.so hello1.o hello2.o -lc $ 余分な PLT が節約できた。

37 まとめ ダイナミックリンクのためには -fpic をつけ てコンパイルするのが定跡。 -fpic をつけないほうがコードはすっきりし ているが、起動時にローダに負担がかか り、コード領域が他のプロセスと共有で きなくなる。 gcc4.0 以降ならばシンボルの visibility を適 切にコントロールすることで実行時シン ボル解決のオーバーヘッドを低減できる。

38 参考文献 “How To Write Shared Libraries” “Linkers & Loaders” オーム社 “BINARY HACKS” オライリージャパン “GNU Development Tools” Wataru Nishida GNU C ライブラリのソース その他たくさんの WEB 検索結果