とあるエンジニアの備忘log
2012年4月9日月曜日
キーボードの LED を光らせるドライバ
「Linuxデバイスドライバプログラミング」の 7-2 章にキーボードの LED を光らせるやり方が書いてあったので、その通りやってみたけど、あれれ?光らないよ。。 そこで、ちょっと調べてみました。 PS/2規格は以下のページを参考にさせてもらいました。 [PS/2 インターフェイスの研究](http://hp.vector.co.jp/authors/VA037406/html/ps2interface.htm) 0x64 のステータスバイトを polling するのがミソらしい。 以下のコードでできました。 #include
#include
#include
MODULE_LICENSE("GPL"); static int sample_init(void) { /* wait while buffer if full*/ while (inb(0x64) & 2); /* set mode indicator */ outb(0xED, 0x60); /* wait while buffer if full*/ while (inb(0x64) & 2); /* bit1: Num Lock */ outb(0x02, 0x60); return 0; } static void sample_exit(void) { while (inb(0x64) & 2); outb_p(0xED, 0x60); while (inb(0x64) & 2); outb_p(0x00, 0x60); } module_init(sample_init); module_exit(sample_exit); `insmod` で Num Lock が光ります。 `rmmod` で消えます。 `outb(0x02, 0x60);` の行を `outb(0x04, 0x60);` や `outb(0x01, 0x60);` にすると Caps Lock や Scroll Lock が光ります。
2012年4月6日金曜日
拡張インラインアセンブラの勉強 2
[前回](/2012/04/1.html)、破壊レジスタを指定して、どういうコードが生成されるか実験してみた。 破壊レジスタに `"memory"` も指定できるが、この使い方がさらにわかりにくい。 そこで簡単な実験をしてみた。 int hoge() { int a = 10; asm("mov r3, #100; str r3, [%0]" : : "r"(&a) : "r3"); return a; } というコードを最適化オプションなしで、コンパイルしてみる。
: push {fp} ; (str fp, [sp, #-4]!) add fp, sp, #0 sub sp, sp, #12 mov r3, #10 str r3, [fp, #-8] sub r2, fp, #8 mov r3, #100 ; 0x64 str r3, [r2] ldr r3, [fp, #-8] mov r0, r3 add sp, fp, #0 pop {fp} ; (ldr fp, [sp], #4) bx lr となった。 毎回、変数の値をメモリまで取りに行くので、これは `100` を返す。 一方、 `-O2` オプションをつけてコンパイルしてみると
: sub sp, sp, #8 add r2, sp, #4 mov r3, #100 ; 0x64 str r3, [r2] mov r0, #10 add sp, sp, #8 bx lr となった。 これは `10` を返す。 インラインアセンブラの中で 変数 `a` が書き変わっているのに気づかないので、即値 `10` を返すように最適化するわけだ。 最適化オプションをつけるかつけないかで、関数の結果が変わるのはもちろん困る。 そこで、インラインアセンブラの中でメモリの中身が書き変わっていますよ、 とコンパイラに教えてあげるために、破壊リストに `"memory"` を追加する。 int hoge() { int a = 10; asm("mov r3, #100; str r3, [%0]" : : "r"(&a) : "r3", "memory"); return a; } をコンパイルすると
: sub sp, sp, #8 mov r3, #10 add r2, sp, #8 str r3, [r2, #-4]! mov r3, #100 ; 0x64 str r3, [r2] ldr r0, [sp, #4] add sp, sp, #8 bx lr となった。 `return` の前にちゃんと変数 `a` をメモリから取り直してくれるので、 `100` が返る。 Linux Kernel にはメモリバリアを挿入するための `barrier()` という関数がある。 定義は `include/linux/compiler-gcc.h` にあって、 /* Optimization barrier */ /* The "volatile" is due to gcc bugs */ #define barrier() __asm__ __volatile__("": : :"memory") となっている。 なんでこれがメモリバリアの役割の果たすのか、ここまで読んできたらほぼ明らかだろう。 `-O2` オプションをつけてコンパイルすると void hoge(int *a, int *b) { *a = 10; *b = *a; } は
: mov r3, #10 str r3, [r0] str r3, [r1] bx lr となる。 最適化によって `*a` からのリードが欠落している。 一方、 void hoge(int *a, int *b) { *a = 10; __asm__ __volatile__("" : : : "memory"); *b = *a; } は
: mov r2, #10 str r2, [r0] ldr r3, [r0] str r3, [r1] bx lr となって、コード通り、 `*a` から `*b` へコピーしている。 コンパイラはメモリバリアを越えた最適化を行わなくなるが、 ハードウェアによる実行時並べ替え(Out of Order 実行)が行われる可能性はあります。 実行時のメモリアクセスの最適化を許すかどうかは ARM の場合はページテーブルの属性で指定されます。
拡張インラインアセンブラの勉強 1
拡張インラインアセンブラ、けっこう難しくてなかなか覚えられません。。 [GCC Inline Assembler](http://caspar.hazymoon.jp/OpenBSD/annex/gcc_inline_asm.html) というサイトを見ながら、もう一回勉強してみた。 基本的に asm( アセンブリテンプレート : 出力オペランド : 入力オペランド : 破壊レジスタ); というフォーマットでいいのですが、「出力オペランド」と「入力オペランド」はわかるとして、 「破壊レジスタ」というのがいまいちピンときません。 そこで、「破壊レジスタ」を指定するのとしないのとで、どのような違いが出るのか、 ARM gcc の逆アセを見ながら勉強してみた。 まずは、普通に(拡張でない)インラインアセンブラを書いた場合 int hoge() { register int a = 10, b = 20; asm("mov r4, #100"); return a + b; } をコンパイルすると
: push {r4, r5, fp} add fp, sp, #8 mov r5, #10 mov r4, #20 mov r4, #100 ; 0x64 add r3, r5, r4 mov r0, r3 sub sp, fp, #8 pop {r4, r5, fp} bx lr となった。 コンパイラは前後関係を考慮することなく、単に `mov r4, #100` を挿入するだけなので、 `30` ではなく、 `110` を返すコードが生成された。 (変数 `b` が `r4` に取られているためだが、これは事前に予測不能である。) そこで拡張インラインアセンブラを使って、 `r4` が破壊されることを指定する。 int hoge() { register int a = 10, b = 20; asm("mov r4, #100" : : : "r4"); return a + b; } は
: push {r4, r5, r6, fp} add fp, sp, #12 mov r6, #10 mov r5, #20 mov r4, #100 ; 0x64 add r3, r6, r5 mov r0, r3 sub sp, fp, #12 pop {r4, r5, r6, fp} bx lr なるほど、 `r4` を避けて、 変数 `a`, `b` を `r6`, `r5` に割り当てています。 しかもちゃんと `r4` をスタックに退避してくれています。 ではコンパイラを困らせてやれと思って、 int hoge() { register int a = 10, b = 20; asm("mov r4, #100" : : : "r4", "r5", "r6", "r7", "r8", "r9", "r10"); return a + b; } とすると、
: push {r4, r5, r6, r7, r8, r9, sl, fp} add fp, sp, #28 sub sp, sp, #8 mov r1, #10 str r1, [fp, #-32] ; 0xffffffe0 mov r2, #20 str r2, [fp, #-36] ; 0xffffffdc mov r4, #100 ; 0x64 ldr r1, [fp, #-32] ; 0xffffffe0 ldr r2, [fp, #-36] ; 0xffffffdc add r3, r1, r2 mov r0, r3 sub sp, fp, #28 pop {r4, r5, r6, r7, r8, r9, sl, fp} bx lr となった。 レジスタ変数をとれなくなって、変数 `a`, `b` はスタックに確保している。 ただし、破壊レジスタに `r11` を指定すると error: fp cannot be used in asm here というエラーが出る。 一応、以下のようにほとんど破壊レジスタリストに指定しても、つじつまの合うように動くようです。 int hoge() { register int a = 10, b = 20; asm("mov r13, #0x81000000" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r12", "r13", "r14"); return piyo(a, b); } をコンパイルすると、
: push {r4, r5, r6, r7, r8, r9, sl, fp, lr} add fp, sp, #32 sub sp, sp, #12 mov r3, #10 str r3, [fp, #-40] ; 0xffffffd8 mov r3, #20 str r3, [fp, #-44] ; 0xffffffd4 mov sp, #-2130706432 ; 0x81000000 ldr r0, [fp, #-40] ; 0xffffffd8 ldr r1, [fp, #-44] ; 0xffffffd4 bl
mov r3, r0 mov r0, r3 sub sp, fp, #32 pop {r4, r5, r6, r7, r8, r9, sl, fp, pc}
register 変数
[前回](/2012/04/blog-post.html)に引き続いて、 レジスタ変数を使った時の逆アセがどうなるかを見てみた。 ARM でやっています。 int hoge() { register int a = 10, b = 20; return a + b; } をコンパイルしてみると
: push {r4, r5, fp} add fp, sp, #8 mov r5, #10 mov r4, #20 add r3, r5, r4 mov r0, r3 sub sp, fp, #8 pop {r4, r5, fp} bx lr なるほど、変数 `a`, `b` を `r5`, `r4` に取るのね。 `r4` - `r11` は復元の必要があるので、いったんスタックに退避してます。 では、 register 変数は何個まで取るんだろうという素直に思ったので、やってみた。 int hoge() { register int a = 10, b = 20, c = 30, d = 40; register int e = 50, f = 60, g = 70, h = 80; return a + b + c + d + e + f + g + h; } をコンパイルしてみると
: push {r4, r5, r6, r7, r8, r9, sl, fp} add fp, sp, #28 sub sp, sp, #8 mov r2, #10 str r2, [fp, #-32] ; 0xffffffe0 mov r9, #20 mov sl, #30 mov r8, #40 ; 0x28 mov r7, #50 ; 0x32 mov r6, #60 ; 0x3c mov r5, #70 ; 0x46 mov r4, #80 ; 0x50 ldr r2, [fp, #-32] ; 0xffffffe0 add r3, r2, r9 add r3, r3, sl add r3, r3, r8 add r3, r3, r7 add r3, r3, r6 add r3, r3, r5 add r3, r3, r4 mov r0, r3 sub sp, fp, #28 pop {r4, r5, r6, r7, r8, r9, sl, fp} bx lr 変数 `b` ~ `h` は `r4` ~ `r10` に割り当てられているが、変数 `a` はレジスタに取れずに、スタックに確保しているみたい。 ちなみに `sl` は `r10` のこと。 レジスタの別名をまとめてみた。 - `r9` = `sb`: スタティックベース - `r10` = `sl`: スタックリミット - `r11` = `fp`: フレームポインタ - `r12` = `ip`: プロシージャコール内スクラッチ - `r13` = `sp`: スタックポインタ - `r14` = `lr`: リンクレジスタ - `r15` = `pc`: プログラムカウンタ
2012年4月5日木曜日
関数のプロローグとエピローグ
関数のプロローグとエピローグにどういう意味があるのか調べてみました。 とりあえず、ARM の gcc コンパイラで実験してみた。 最適化オプション (`-O`) はつけてない。 void hoge() { } をコンパイルすると
: push {fp} ; (str fp, [sp, #-4]!) add fp, sp, #0 add sp, fp, #0 pop {fp} ; (ldr fp, [sp], #4) bx lr となった。 `fp` は `r11` のこと。フレームポインタの意味らしい。 - 関数に入るとき(プロローグ)に `fp` をスタックに退避し、 `sp` の値を `fp` にコピー。 - 関数から出るとき(エピローグ)に `fp` を `sp` にコピーしたあと、 `fp` を復元。 となっているが、これだけ見ると、なんでこんな周りくどいことするのかよく理解できない。 そこで int hoge() { int a = 10; return a; } をコンパイルしてみた。
: push {fp} ; (str fp, [sp, #-4]!) add fp, sp, #0 sub sp, sp, #12 mov r3, #10 str r3, [fp, #-8] ldr r3, [fp, #-8] mov r0, r3 add sp, fp, #0 pop {fp} ; (ldr fp, [sp], #4) bx lr となった。 これを見ると、 `sp` を `fp` へコピーした後、スタックを `12` 伸ばしている。 この時点で、 `sp = fp - 12` という関係がある。 例えば、関数に入った時点の `sp` の値が `0x80000000` だったとして、 スタックの使い方は 0x7ffffff0: 未使用 <-- sp の指す位置 0x7ffffff4: 変数 a 0x7ffffff8: 未使用 0x7ffffffc: 復帰用fp <-- fp の指す位置 となっているはず。 さらに int hoge() { int a = 10, b = 20; return a + b; } をコンパイルしてみると
: push {fp} ; (str fp, [sp, #-4]!) add fp, sp, #0 sub sp, sp, #12 mov r3, #10 str r3, [fp, #-8] mov r3, #20 str r3, [fp, #-12] ldr r2, [fp, #-8] ldr r3, [fp, #-12] add r3, r2, r3 mov r0, r3 add sp, fp, #0 pop {fp} ; (ldr fp, [sp], #4) bx lr となった。 スタックの使い方は 0x7ffffff0: 変数 b <-- sp の指す位置 0x7ffffff4: 変数 a 0x7ffffff8: 未使用 0x7ffffffc: 復帰用fp <-- fp の指す位置 さらにさらに int hoge() { int a = 10, b = 20, c = 30; return a + b + c; } は
: push {fp} ; (str fp, [sp, #-4]!) add fp, sp, #0 sub sp, sp, #20 mov r3, #10 str r3, [fp, #-8] mov r3, #20 str r3, [fp, #-12] mov r3, #30 str r3, [fp, #-16] ldr r2, [fp, #-8] ldr r3, [fp, #-12] add r2, r2, r3 ldr r3, [fp, #-16] add r3, r2, r3 mov r0, r3 add sp, fp, #0 pop {fp} ; (ldr fp, [sp], #4) bx lr となった。 スタックは 0x7fffffe8: 未使用 <-- sp の指す位置 0x7fffffec: 変数 c 0x7ffffff0: 変数 b 0x7ffffff4: 変数 a 0x7ffffff8: 未使用 0x7ffffffc: 復帰用fp <-- fp の指す位置 となっているはず。 以上を見ると、 - `fp` と `sp` の間に自動変数を確保しているらしい。 - `sp` の伸ばす量は 8 ずつ増えている。 という規則があることがわかった。 ただし、上記の例で、 `0x7ffffff8` の位置が常に未使用になっているのがちょっと不思議です。 そこで、以下の関数をコンパイルしてみて納得。 int hoge() { int a = 10, b = 20; return piyo(a, b); }
: push {fp, lr} add fp, sp, #4 sub sp, sp, #8 mov r3, #10 str r3, [fp, #-8] mov r3, #20 str r3, [fp, #-12] ldr r0, [fp, #-8] ldr r1, [fp, #-12] bl
mov r3, r0 mov r0, r3 sub sp, fp, #4 pop {fp, pc} となった。 なるほど、この場合は `fp` と `lr` の両方を退避しておく必要があるのね。 0x7ffffff0: 変数 b <-- sp の指す位置 0x7ffffff4: 変数 a 0x7ffffff8: 復帰用fp 0x7ffffffc: lr <-- fp の指す位置 ちなみに、 ABI によると、 `r0-r3`, `r12` は復元しなくてよいレジスタ、 `r4-r11` は復元しなくてはならないレジスタです。
新しい投稿
前の投稿
ホーム
登録:
投稿 (Atom)