Altanative macro

Preview:

DESCRIPTION

To explain how to work alternative() macro in Linux kernel.

Citation preview

altanative マクロで学ぶ gas入門kosaki@ ふじつう ?

今日はなんの話?• 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:

mfence664:.previous

なんかセクションが3つ出てきた

なんかコードっぽいこっちはデータかな?

セクションって何?• バイナリのカタマリの単位• 最低でもコードとデータの2つのセクションが出来るのが一般的( OS 問わず)• elf は作ろうと思えば好きなだけセクションを作れる仕様• 作って意味があるかは別の話ww

OS や標準ライブラリ・コマンドが使わない情報の意味って・・・

ここまで来てから、やっと、gas のマニュアルを読む

なんちゃって理解重要!

セクション切り替え.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ってインラインアセンブラ前提の拡張多い

アライン整列 擬似命令• align [argument]後続の命令・データを argument byte にアライメントさせる。 • んー、説明いるのかな?

即値埋め込み擬似命令.byte number.long number

引数の number をその場所に埋め込む。単純。普通は定数データを埋め込むのに使う。マシン語さえ分かっていれば、これだけで何でも出来る

すごくダメな例• カーネルの 32bit移行直後

.byte 0x66 0xeacode32: .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**0CONTENTS, ALLOC, LOAD, READONLY, CODE13 .fini 0000001a 08048424 08048424 00000424 2**2CONTENTS, ALLOC, LOAD, READONLY, CODE14 .rodata 00000008 08048440 08048440 00000440 2**2CONTENTS, ALLOC, LOAD, READONLY, DATA15 .altinstructions 00000017 08048448 08048448 00000448 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

さっきの、なんちゃって main.c をコンパイルして objdumpa.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 };

実は以下のデータ構造をむりやり作ってるんです。

むりやりまとめる(ここまでの結論)• alternative マクロは、 oldinstr 引数だけを実行コードとして生成する。• newinstr 引数、 feature 引数を使って、 .altinstr_replacement と .altinstructions というなんだかよく分からないデータ構造を作っている。

oldinstr しか実行されない!!

まあ、おちつけ

そんなわけ無いから

リンカスクリプトを見よう• セクションを作った以上どこかで使ってるはず• リンカスクリプトで使わずどこで使えると?

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年早いっすよ

やってみようよ!!

ご清聴ありがとうございました!!