Upgrade to Pro — share decks privately, control downloads, hide ads and more …

alternativeマクロで学ぶgas入門.pdf

 alternativeマクロで学ぶgas入門.pdf

kosaki

May 04, 2023
Tweet

More Decks by kosaki

Other Decks in Programming

Transcript

  1. 今日はなんの話? • x86のsmp_mb(), smp_wmb(), smp_rmb()などの 実装に使われている altanativeマクロの解説 • ・・・をサカナにして、gasの独特の記法とかカーネル でアヤシゲなセクションが出てきたらまずどこから調

    べるべきか。とか、そういう入門話をするつもり • 最近レベルが上がりすぎているカーネル読書会に へたれな発表をお届け • 発表者の敷居を下げるのにちょっと貢献?
  2. あなたは誰? • さあ? • 最近自分でも専門が分からない • 昔XMLとかやってて、データ放送関係に引っ張り込まれ、ブ ラウザを三回ぐらいイチから作ったりしているうちにカーネル とかXとかアセンブラとかメモリバリアとかlow levelな話に詳

    しくなった。 • その後、今の会社に転職。面談で甘い言葉をいろいろと囁か れたので • え、現在の仕事ですか? いやあ、あんまり悪口いえないん ですよ。 • 最近、レガシーエンコーディング界隈もウロウロと • 人見知りなのでお手柔らかに ( ̄ω ̄;)ゞ
  3. なんで出てきたの? • 1月ぐらいからBlogをつけてみた。 もちろん(?) ギャグとネタとお笑いがメイン • ある時、ブログにspinlockという単語が入っていた ら”spinlock トイレ” というアヤシイ検索ワードでた

    どり着く人が・・・ • 特殊な使命感にかられ、トイレ以外のロックを説明 する • カーネルの話は誰も反応くれないので、最近飽き気 味 • 自分にカツを入れるために発表するか! ← おい
  4. では始めましょう 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にも似たような コードがあるらしい・・
  5. 定義 #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") なにこの暗号? てゆーか、ジャンプ命令どこ? これを見た瞬間理解できる人は少ない
  6. 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 */ この一文は、後で超重要なヒントに
  7. むりやりアセンブラを展開してみる テストプログラムにむりやり貼っつける 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(); }
  8. むりやりアセンブラを展開してみる $ 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つ出てきた なんかコードっぽい こっちはデータかな?
  9. セクション切り替え .section [section_name], “[FLAGS]” セクションを切り替える section_name: 任意の文字列。先頭は”.”(ドット)で始めるのが礼儀作法みたい FLAGS: ‘a’ allocatable,

    実行時にメモリにロードされる。 これがOFFなセクションってほとんど見ない ‘x’ executable, 実行可能 .previous 1つ前の.section命令をキャンセルする。 663: mfence 664: .section .altinstr_replacement,"ax" .previous むむ、テキストセクションじゃない セクションにコードを入れてますな
  10. ローカルなラベル ラベルを参照するときは必ず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ってインラインアセンブラ前提の拡張多い
  11. すごくダメな例 • カーネルの32bit移行直後 .byte 0x66 0xea code32: .long 0x1000 .word

    0x10 ここ、受け売りなので、質問禁止 16bitアセンブラのファイルにむりやり32bitジャンプ命令を入れるぜ。 ↓ こんなコード 0x66 0xea 0x1000 0x10 次の命令 32bit命令ね jmp オフセット セレクタ
  12. 即値埋め込み擬似命令 • .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 アドレスが入るっぽげ コード長が入るっぽげ
  13. 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 中でのセクションのファイルオフセットとサイズが分かるので・・・
  14. 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 へのポインタ へのポインタ
  15. 実は 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 }; 実は以下のデータ構造を むりやり作ってるんです。
  16. リンカスクリプトを見よう • セクションを作った以上どこかで使ってるはず • リンカスクリプトで使わずどこで使えると? 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言語でいうグローバル変数)を作ってるぞ!! それぞれ、先頭と 最後をさすポインタ
  17. __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 見る カーネル起動時に 呼ばれる
  18. 書き換えだぜ! 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でコード上書き
  19. 本日主張したかったこと • マクロ中でインラインアセンブラされるとCとアセンブラが混じってとても読 み肉い • gasの拡張はドキュメント少なくてとても困る そんな時 kosaki メソッド 誕生!!

    • とりあえず動かしてみる。 • ついでにバイナリダンプとかしてみる • バイナリは嘘つかない ! もちろん ネタです 信じないでください