Share
Embed
Transcript
31 slides
-
今日はなんの話? • x86のsmp_mb(), smp_wmb(), smp_rmb()などの 実装に使われている altanativeマクロの解説 • ・・・をサカナにして、gasの独特の記法とかカーネル でアヤシゲなセクションが出てきたらまずどこから調
べるべきか。とか、そういう入門話をするつもり • 最近レベルが上がりすぎているカーネル読書会に へたれな発表をお届け • 発表者の敷居を下げるのにちょっと貢献? -
あなたは誰? • さあ? • 最近自分でも専門が分からない • 昔XMLとかやってて、データ放送関係に引っ張り込まれ、ブ ラウザを三回ぐらいイチから作ったりしているうちにカーネル とかXとかアセンブラとかメモリバリアとかlow levelな話に詳
しくなった。 • その後、今の会社に転職。面談で甘い言葉をいろいろと囁か れたので • え、現在の仕事ですか? いやあ、あんまり悪口いえないん ですよ。 • 最近、レガシーエンコーディング界隈もウロウロと • 人見知りなのでお手柔らかに ( ̄ω ̄;)ゞ -
なんで出てきたの? • 1月ぐらいからBlogをつけてみた。 もちろん(?) ギャグとネタとお笑いがメイン • ある時、ブログにspinlockという単語が入っていた ら”spinlock トイレ” というアヤシイ検索ワードでた
どり着く人が・・・ • 特殊な使命感にかられ、トイレ以外のロックを説明 する • カーネルの話は誰も反応くれないので、最近飽き気 味 • 自分にカツを入れるために発表するか! ← おい -
では始めましょう altanativeマクロを使っている例 #define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM) いわゆる
if – else マクロ if( X86_FEATURE_XMM ){ ← SSE搭載CPUか return "asm(sfence)" }else { return "asm(lock; addl $0,0(%%esp))" } のように動作する(x86のみ存在するマクロ) ・・・・なら、そう書けばいいじゃん! なんでそうしないの? スタックポインタの指すメモリに 0を足す (もちろん何もおきない) 実はPPCにも似たような コードがあるらしい・・ -
定義 #define alternative(oldinstr, newinstr, feature) ¥ asm volatile ("661:¥n¥t" oldinstr
"¥n662:¥n" ¥ ".section .altinstructions,¥"a¥"¥n" ¥ " .align 4¥n" ¥ " .long 661b¥n" /* label */ ¥ " .long 663f¥n" /* new instruction */ ¥ " .byte %c0¥n" /* feature bit */ ¥ " .byte 662b-661b¥n" /* sourcelen */ ¥ " .byte 664f-663f¥n" /* replacementlen */ ¥ ".previous¥n" ¥ ".section .altinstr_replacement,¥"ax¥"¥n" ¥ "663:¥n¥t" newinstr "¥n664:¥n" /* replacement */ ¥ ".previous" :: "i" (feature) : "memory") なにこの暗号? てゆーか、ジャンプ命令どこ? これを見た瞬間理解できる人は少ない -
altanativeマクロのコメント 288 /* 289 * Alternative instructions for different CPU
types or capabilities. 290 * 291 * This allows to use optimized instructions even on generic binary 292 * kernels. 293 * 294 * length of oldinstr must be longer or equal the length of newinstr 295 * It can be padded with nops as needed. 296 * 297 * For non barrier like inlines please define new variants 298 * without volatile and memory clobber. 299 */ この一文は、後で超重要なヒントに -
むりやりアセンブラを展開してみる テストプログラムにむりやり貼っつける main.c #define alternative(oldinstr, newinstr, feature) asm volatile ("661:¥n¥t"
oldinstr “¥n662:¥n" ".section .altinstructions,¥"a¥“¥n" " .align 4¥n" " .long 661b¥n" /* label */ " .long 663f¥n" /* new instruction */ " .byte %c0¥n" /* feature bit */ " .byte 662b-661b¥n" /* sourcelen */ " .byte 664f-663f¥n" /* replacementlen */ ".previousn" ".section .altinstr_replacement,¥"ax¥“¥n" "663:¥n¥t" newinstr “¥n664:¥n" /* replacement */ ".previous" :: "i" (feature) : "memory") #define X86_FEATURE_XMM2 57 /* dummy */ #define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2) main(){ mb(); } -
むりやりアセンブラを展開してみる $ gcc -S main.c -o main.s 661: lock; addl
$0,0(%esp) 662: .section .altinstructions,"a" .align 4 .long 661b .long 663f .byte 57 .byte 662b-661b .byte 664f-663f .previous .section .altinstr_replacement,"ax" 663: mfence 664: .previous なんかセクションが 3つ出てきた なんかコードっぽい こっちはデータかな? -
セクションって何? • バイナリのカタマリの単位 • 最低でもコードとデータの2つのセクションが 出来るのが一般的(OS問わず) • elfは作ろうと思えば好きなだけセクションを作 れる仕様 •
作って意味があるかは別の話ww OSや標準ライブラリ・コマンドが使わない情報の意味って・・・ -
セクション切り替え .section [section_name], “[FLAGS]” セクションを切り替える section_name: 任意の文字列。先頭は”.”(ドット)で始めるのが礼儀作法みたい FLAGS: ‘a’ allocatable,
実行時にメモリにロードされる。 これがOFFなセクションってほとんど見ない ‘x’ executable, 実行可能 .previous 1つ前の.section命令をキャンセルする。 663: mfence 664: .section .altinstr_replacement,"ax" .previous むむ、テキストセクションじゃない セクションにコードを入れてますな -
ローカルなラベル 661: lock; addl $0,0(%esp) 662: 同じ関数内に2つ以上あってもエラーにならないラベル ラベル名が数値だけ 661: lock;
addl $0,0(%esp) 662: どうやって参照するのかは次ページで -
ローカルなラベル ラベルを参照するときは必ずfかbサフィックスをつける 参照位置から一番近いラベルを 参照 fは前向きに(forward)、 bは後ろ向きに (backword) サーチして一番近いもの 661: lock;
addl $0,0(%esp) 662: jmp 661f 661: lock; addl $0,0(%esp) 662: jmp 661b マクロで内緒でインラインアセンブラしてる時(今回のケース)では 人間がラベルぶつからないように調整できないから。 ほんとgasってインラインアセンブラ前提の拡張多い -
すごくダメな例 • カーネルの32bit移行直後 .byte 0x66 0xea code32: .long 0x1000 .word
0x10 ここ、受け売りなので、質問禁止 16bitアセンブラのファイルにむりやり32bitジャンプ命令を入れるぜ。 ↓ こんなコード 0x66 0xea 0x1000 0x10 次の命令 32bit命令ね jmp オフセット セレクタ -
即値埋め込み擬似命令 • .byte や .longの引数は単純な四則演算を受け付ける • .long 661b と書けばラベル661を前方参照してそのアドレスを埋め込む •
.byte 662b-661b と書けば、と書けばラベル662のアドレス引く661のアドレス、 つまりその範囲のコードの長さが埋め込まれる。 .section .altinstructions,"a" .align 4 .long 661b .long 663f .byte 57 .byte 662b-661b .byte 664f-663f .previous アドレスが入るっぽげ コード長が入るっぽげ -
objdump(readelfでも可) セクション: 索引名 サイズ VMA LMA File off Algn (中略)
12 .altinstr_replacement 00000006 0804841c 0804841c 0000041c 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .fini 0000001a 08048424 08048424 00000424 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .rodata 00000008 08048440 08048440 00000440 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 15 .altinstructions 00000017 08048448 08048448 00000448 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA さっきの、なんちゃって main.c をコンパイルして objdump a.out 中でのセクションのファイルオフセットとサイズが分かるので・・・ -
altinstructions部分をod してみる 50 83 04 08 1c 84 04 08
39 06 03 00 56 83 04 08 1f 84 04 08 39 06 03 ---------------- --------------- oldinstr newinstr pad (4byte alignに するため) mfence命令のopcodeは 0F AE /6 の3バイト add mem imm8で5バイトlock prefixで+1 feature(X86_FEATURE_XMMを ダミーで0x39でごまかしたから ) od -A x -t x1 -j 0x448 -N 0x17 a.out .section .altinstructions,"a" .align 4 .long 661b .long 663f .byte 57 .byte 662b-661b .byte 664f-663f .previous へのポインタ へのポインタ -
実は 50 83 04 08 1c 84 04 08 39
06 03 00 56 83 04 08 1f 84 04 08 39 06 03 oldinstr newinstr linux/include/asm-i386/system.h の 278 struct alt_instr { 279 __u8 *instr; /* original instruction */ 280 __u8 *replacement; 281 __u8 cpuid; /* cpuid bit set for replacement */ 282 __u8 instrlen; /* length of original instruction */ 283 __u8 replacementlen; /* length of new instruction, <= instrlen */ 284 __u8 pad; 285 }; 実は以下のデータ構造を むりやり作ってるんです。 -
リンカスクリプトを見よう • セクションを作った以上どこかで使ってるはず • リンカスクリプトで使わずどこで使えると? linux/arch/i386/kernel/vmlinux.lds.S 86 . = ALIGN(4);
87 __alt_instructions = . ; 88 .altinstructions : { *(.altinstructions) } 89 __alt_instructions_end = . ; 90 .altinstr_replacement : { *(.altinstr_replacement) } __alt_instructions と __alt_instructions_end という シンボル(C言語でいうグローバル変数)を作ってるぞ!! それぞれ、先頭と 最後をさすポインタ -
__alt_instructionsでgrep linux/arch/i386/kernel/setup.c 1318 static int no_replacement __initdata = 0; 1319
1320 void __init alternative_instructions(void) 1321 { 1322 extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; 1323 if (no_replacement) 1324 return; 1325 apply_alternatives(__alt_instructions, __alt_instructions_end); 1326 } 1327 1328 static int __init noreplacement_setup(char *s) 1329 { 1330 no_replacement = 1; 1331 return 0; 1332 } 1333 1334 __setup("noreplacement", noreplacement_setup); 型はどこから? と思ってはいけない。 リンカスクリプトに 型はないので 宣言したもの勝ち 次のページで apply_alternatives 見る カーネル起動時に 呼ばれる -
書き換えだぜ! 1291 void apply_alternatives(void *start, void *end) 1292 { 1293
struct alt_instr *a; 1294 int diff, i, k; 1295 unsigned char **noptable = intel_nops; (中略) 1302 for (a = start; (void *)a < end; a++) { 1303 if (!boot_cpu_has(a->cpuid)) 1304 continue; 1305 BUG_ON(a->replacementlen > a->instrlen); 1306 memcpy(a->instr, a->replacement, a->replacementlen); 1307 diff = a->instrlen - a->replacementlen; 1308 /* Pad the rest with nops */ 1309 for (i = a->replacementlen; diff > 0; diff -= k, i += k) { 1310 k = diff; 1311 if (k > ASM_NOP_MAX) 1312 k = ASM_NOP_MAX; 1313 memcpy(a->instr + i, noptable[k], k); 1314 } 1315 } 1316 } linux/include/asm-i386/system.h の 278 struct alt_instr { 279 __u8 *instr; 280 __u8 *replacement; 281 __u8 cpuid; 282 __u8 instrlen; 283 __u8 replacementlen; 284 __u8 pad; 285 }; CPUがサポートしていな ければスキップ memcpyでむりやりコード書き換え 新旧でコードの長さが不一致の 時は残りをNOPで埋める 1~7バイトの多彩なNOP命令を 生かして出来る限り少ないNOP 命令で埋める 作ったデータ構造を生かしてfeatre引数を満たしていれば newinstrでコード上書き -
結論 • altanativeマクロはif-elseをジャンプ命令を使わず実現したいときに使う 自己書き換えマクロである • smp_wmb()などはシーケンスロックとかRCU Updateとかの軽量ロック で使われるので性能重要 • Pentium4なのにlock
addlするのも、ジャンプ命令走るのはイヤイヤ (゚Д゚;≡;゚Д゚) ハックするか! という思考回路が働いたと思われる -
本日主張したかったこと • マクロ中でインラインアセンブラされるとCとアセンブラが混じってとても読 み肉い • gasの拡張はドキュメント少なくてとても困る そんな時 kosaki メソッド 誕生!!
• とりあえず動かしてみる。 • ついでにバイナリダンプとかしてみる • バイナリは嘘つかない ! もちろん ネタです 信じないでください -
本日、本当に主張したかったこと • gcc拡張とかgas拡張とか慣れないとよく分からなくて、カー ネル初心者に敷居が高くなってると思うんですよ • でも、やってみると以外に分かったりする。 • ソースコードをいきなり読んで分からない所があるのは当た り前 •
それで挫折した気になるなんて挫折プロのオイラからみたら 10年早いっすよ やってみようよ!!