xv6-armv7 (4)
最近xv6-armv7なんてのに手を出しているけど、発端となった本家xv6の方でも、64Bitの石で 動かしたいって欲望があるみたいで、一部の方が取り組んでおられる。
16ビットモードで起動する糞石を32ビットモードに苦心してあげて、そこから更に64ビットモードの 高みを目指すんだそうだ。なんとも苦労なこったい、眩暈がしますよ。
それもこれも、インテルの顧客大事な精神の発露だろう。そんなに顧客が大事なら、 電卓用の石i4004モードから始めるべきだぞ。起動したら電卓が使えるようになりまーすって 顧客を引きつける言葉だ。
そこまで古くならなくても、1972年発表の8008モードからスタートするのが良いだろう。 昔のインテルはメモリー屋をやってた。世間を席巻したi1103ってメモリーが在るんよ。 肩書きは、大容量1bit x 1024adress ダイナミックメモリー。こいつがじゃじゃ馬でね。 プリチャージって名前のクロックが必要なのよ。そのクロックを基準にチップイネーブルとかの タイミングがきっちりと決められていた。すこしでもタイミングがずれるとすぐ健忘症に なるんだ。姉妹品におしとやかなi1101ってのがあったな。こちらはスタチックメモリー。 容量は1bit x 256Addressだった。
この他にも、紫外線を当てて消毒するとメモリー内のbug(も)退散するって言う、EPROMとか、 電流を流して回路を溶断するって方法で洗脳して一生記憶が消えないようにしちゃうメモリーとかも作ってた。むしろ メモリー作りが本職のメーカーだったんだ。でも、電卓ICの注文を日本から受けてi4004を 作った。(電卓ICの話に乗ってくれたのはインテルだけだったとか) 競合ひしめくメモリー業界にいるより、電卓ICを発展させて汎用のCPUを作った方が、 絶対儲かる、そう思ったんだなムーアさんは。それには、顧客第一にしなきゃね。
このムーアさん、最近ノーベル賞を取った大村名誉教授と一緒だな。曰く、人と同じ事を やってたら絶対一番になれないと。
かくして、サービス精神で、1978年発表の8086からずっと、昔のモードを引きずるように なった。いいかげんどこかで、昔のしがらみを断ち切りたいと、新種のCPUを世に問うたけど、 受け入れられず。ユーザーは超保守的。ああ、ここで言うユーザーってのは、一般ユーザー じゃなくて、石を採用して製品を作りあげるメーカーの事ね。まあ、メーカーもゲイツ大王の OSが使えなくなる事を恐れて、石の呪縛から逃げ出せなかったってのが真相かな。
インテルとマイクロソフトの陰謀で、使う石もOSも固定されちゃった。それ以外の選択子は 実質無かった。そこに風穴を開けたのは、欧州はエゲレズ生まれのARM。地面の下からのし上がって きたって感じ。そこらのねーちゃんのご愛用品になってる。ねーちゃん達に支持されると、 これはもう鬼に金棒ですよ。
今、オイラーは『FreeBSDのブートシーケンスを読む』なんて、今な亡きユニマガの連載を まとめたムックを再読してるけど、コールゲートだとかIOデスクリプターテーブルだとか、 インテルの糞石仕様書を丹念に読まされているみたいで、辛い辛い苦行の日々。 ARMな石も変態だけど、糞石よりはずっとましだと思う今日この頃。
ああ、この枕、 自作エミュレータで学ぶx86アーキテクチャ-コンピュータが動く仕組みを徹底理解! のちら見を見て、書いたものです。著者の方達、頑張ってますなあ。若いって素敵!!!
ARMな石は、ある方に言わせると、ArmはRiscだが, 命令幅が32ビットで, 水平型マイクロプログラムの様相を持つ との事。そして、組み込みを意識して16bitモード(縮小命令系)も持ってる。世を席巻した (過去形に注意)JAVAを直接実行するモードも好き者のために用意されてる。どうせなら、 schemeモードも埋め込んで欲しかったぞ。
リベンジ
前回だったか、Fedoraでxv6-armv7をやろうとしたら、コンパイルでraiseがねーぞって言われて 拒否されてしまっていた。今回はそのあだ討ち。
raiseが無ければ、適当にでっちあげればいいんでないかい。
[sakae@fedora xv6-armv7]$ cat lib/raise.S .globl raise raise: nop
raiseって騒ぎを起こすやつよね。オイラーが定義したのは脱力するぐらい簡単なやつ、柳に風って ごとくさらりとしたやつ。単に名前が欲しかっただけ、名義を借りただけ。これで、コンパイルが 通った。どうコンパイルされたか、ユーザーランドを見ると
00000fd8 <raise>: fd8: e1a00000 nop ; (mov r0, r0) 11bc: e3500000 cmp r0, #0 11c0: 13e00000 mvnne r0, #0 11c4: ea000007 b 11e8 <__aeabi_idiv0> 000011e8 <__aeabi_idiv0>: 11e8: e92d4002 push {r1, lr} 11ec: e3a00008 mov r0, #8 11f0: ebffff78 bl fd8 <raise> 11f4: e8bd8002 pop {r1, pc}
nopなんて命令はARMな石は持っていなくて、代用でmov r0,r0 なんてのが使われている。 で、これで実行してみると、
[sakae@fedora xv6-armv7]$ ./run.sh run armv7 without qemu.log : starting xv6 for ARM... cpu0: panic: popcli - interruptible 15: 0x0 14: 0x0 13: 0x0 12: 0x0 11: 0x0 10: 0x0 9: 0x0 8: 0x0 7: 0x0 6: 0x0 5: 0xc0020750 4: 0xc0025e74 3: 0xc00262c0 2: 0xc00206c0 1: 0xc0021c68
見事に返り打ちされましたよ。どういう負け方をしたか記憶に留めておいて再復讐を誓うのであります。
(gdb) b panic Breakpoint 1 at 0xc0021c2c: file console.c, line 123. (gdb) c Continuing. Breakpoint 1, panic (s=0xc002a0a8 "popcli - interruptible") at console.c:123 123 cli(); (gdb) bt #0 panic (s=0xc002a0a8 "popcli - interruptible") at console.c:123 #1 0xc00206c0 in popcli () at arm.c:74 #2 0xc00262c0 in release (lk=0xc00ae60c <ptable>) at spinlock.c:72 #3 0xc0025e74 in forkret () at proc.c:400 #4 0xc0020750 in popcli () at arm.c:83 Backtrace stopped: previous frame inner to this frame (corrupt stack?)
panicに落ちる寸前に動いてた所は、popcliの中のint_enabled()の判定部分。この場合のintって 整数じゃなくて割り込みだな。同じ省略文字列でも、コンテキストで読み替えなきゃならないって のは大変。
そんな訳で、割り込みの復讐じゃなかった復習。
割り込み
上記のint_enabled()は、
int int_enabled () { uint val; // ok, enable paging using read/modify/write asm("MRS %[v], cpsr": [v]"=r" (val)::); return !(val & DIS_INT); }
C語のくせに、アセンブラーがちょびっと顔を出している。MRSって大文字で書かれている事から どうも、GNU特有のマクロっぽいぞ。何となく、値をvalってC側の変数に持ってきてるようだって 想像がつく。 そういう場合は、マクロ展開されたCPUに近い所で見るのが正解かな。
sakae@uB:~/xv6-armv7/src$ arm-linux-gnueabi-objdump -d build/arm.o : 000000a8 <int_enabled>: a8: e52db004 push {fp} ; (str fp, [sp, #-4]!) ac: e28db000 add fp, sp, #0 b0: e24dd00c sub sp, sp, #12 b4: e10f3000 mrs r3, CPSR b8: e50b3008 str r3, [fp, #-8] bc: e51b3008 ldr r3, [fp, #-8] c0: e2033080 and r3, r3, #128 ; 0x80 c4: e3530000 cmp r3, #0 c8: 13a03000 movne r3, #0 cc: 03a03001 moveq r3, #1 d0: e6ef3073 uxtb r3, r3 d4: e1a00003 mov r0, r3 d8: e24bd000 sub sp, fp, #0 dc: e49db004 pop {fp} ; (ldr fp, [sp], #4) e0: e12fff1e bx lr
mrsって何だ? 動作モードの項にある、PSRの 読み書きだな。r3にCPSRの値を取り出してきて、IRQが建っていない事(割り込み禁止)に なってる事を確認してるんだな。C語のコメント、間違ってるじゃん。
近傍にあるcliとかstiの中に、
asm("MSR cpsr_cxsf, %[v]": :[v]"r" (val):);
こういうのがあるけど、どう翻訳されてるか見ると
24: e12ff003 msr CPSR_fsxc, r3
になっていた。動作モードの説明を鑑みると、cpsrってのは4バイト構成のレジスター。それぞれの バイトに名前が付いてる。そして転送はバイト単位で出来るとな。そのフラグにfsxcって 指定されてるんで、結局、4バイトの転送を指示してる。
なお、ARMの変態な所として、割り込みが入るとCPUのモードが変わり、途端に専用のPSRが 幅を利かせるのがある。そのためにSPSRって、PSRの保存用のものが用意はされているんだけどね。 それにしても多重割り込みとかが発生した時の事を考えると、自前でハンドリングする 必要があり面倒だ。いえ、そこまではオイラーはしません。組み込み屋さんの愚痴を聞いて あげているんです。
それやこれや、面倒なしきたりが無駄にあるので、 初期化処理なんて章をたてて、説明 されてますね。
あれ? ひょっとして msrって、特権命令?
例外処理
特権命令を特権が無い状態で実行すると、未定義例外が起きるそうだ。きっと上の疑問も 例外が処理してくれるのだろう。それより、システムコールも例外処理に頼っている。 カーネルを読む時には、避けて通れない部分になる。勿論石の特性に合わせてコードが 書かれている。
ARMの石の場合は、例外発生時のプロセッサの動作 な風に動くらしい。例外が起きると、プロセッサーは、決まった番地から実行を始める。 どの例外で、どこから実行するかってのが、メモリーの決まった番地にあらかじめ定義 しておく。そのエリアの事をベクターテーブルなんていう。
このテーブルは、メモリーの0番地付近にも、うんと高い所にも置ける。xv6では、うんと 高い所に置くようにARMを設定してる。で、このテーブルがどこで設定されているかと言うと、
// the opcode of PC relative load (to PC) instruction LDR pc, [pc,...] static uint32 const LDR_PCPC = 0xE59FF000U; // create the excpetion vectors ram_start = (uint32*)VEC_TBL; // 0xFFFF0000 ram_start[0] = LDR_PCPC | 0x18; // Reset (SVC) ram_start[1] = LDR_PCPC | 0x18; // Undefine Instruction (UND) ram_start[2] = LDR_PCPC | 0x18; // Software interrupt (SVC) : ram_start[8] = (uint32)trap_reset; ram_start[9] = (uint32)trap_und; ram_start[10] = (uint32)trap_swi; :
trap.c/trap_init()の中で定義されてた。ram_startって言う0xFFFF0000番地からの 配列に置いてある。システムコールが発生した時、trap_swiへ飛んで行きたいんだけど、 そこに直接jmpは出来ないので、間接的にjmpする命令がram_startの前半に置かれている。
続けて、trap_swiを追ってみると、trap_asm.Sに
# handle SWI, we allow nested SWI trap_swi: # build trapframe on the stack STMFD sp!, {r0-r12, r14} // save context MRS r2, spsr // copy spsr to r2 STMFD r13!, {r2} // save r2(spsr) to the stack STMFD r13!, {r14} // save r14 again to have one uniform trapframe STMFD r13, {sp, lr}^ // save user mode sp and lr SUB r13, r13, #8 # call traps (trapframe *fp) MOV r0, r13 // copy r13_svc to r0 BL swi_handler // branch to the isr_swi
少々複雑な事をやってるけど、多重割り込みにも対応してるって、pcを除く全レジスタとspsrを スタックに積んで、swi_handlerに飛んで行く。なお、割り込みハンドラーに、これらの レジスター群の値を渡す為、r13の値をr0に代入してる。これらのレジスタを直接扱う部分はC語で 書けないのでアセンブラーで書いてある。
ついでに、割り込みから戻る部分
trapret: LDMFD r13, {sp, lr}^ // restore user mode sp and lr ADD r13, r13, #8 LDMFD r13!, {r14} // restore r14 LDMFD r13!, {r2} // restore spsr MSR spsr_cxsf, r2 LDMFD r13!,{r0-r12, pc}^ // restore context and return
スタックに積んだのと逆に、レジスターに戻している。但し、PSRだけは、例外発生時に saveして置いたspsrに戻している。。
swi_handlerはtrap.cに置かれた、あっさりしたコードだ。
8// trap routine 9void swi_handler (struct trapframe *r) 10{ 11=> proc->tf = r; 12 syscall (); 13}
ここで言うrってのは、r0の事、すなわちr13(SP)の事だ。例外発生時に保存しておいたレジスター群 を、C語側にコピペしてから、システムコールの処理に取り掛かるとな。
未定義命令
PSRの制御パートを変更する命令は特権階級。下々のユーザーランドで実行したら、怒られると 思うんだ。やってみた。
int main(int argc, char *argv[]) { uint val; sti(); asm("MRS %[v], cpsr": [v]"=r" (val)::); printf(1, "%d\n", !(val & DIS_INT) ); cli(); exit(); }
stiとかcliってのは、カーネル側からぱくってきたやつ。実行しても怒られなかった。 なぜ? たまたま見てた今日の読み物の中に説明が有った。ユーザーモードでCPSRの 変更は、無視されるだけで、例外は起きないとの事。
ならば、プログラムを変更して、spsrへ書き込むようにしよう。ユーザーモードでは、spsrの 裏レジスターは無いから、これはもうどうやっても、未定義例外が上がるはず。
$ test und at: 0x28
表示された 0x28 ってのは何?
sakae@uB:~/xv6-armv7/src$ grep "und at" *.c trap.c: cprintf ("und at: 0x%x \n", r->pc);
pcの値を表示するとな。
|0x18 <cli+24> orr r3, r3, #128 ; 0x80 | |0x1c <cli+28> str r3, [r11, #-8] | |0x20 <cli+32> ldr r3, [r11, #-8] | >|0x24 <cli+36> msr SPSR_fsxc, r3 | |0x28 <cli+40> sub sp, r11, #0 | |0x2c <cli+44> pop {r11} ; (ldr r11, [sp], #4) |
未定義命令を実行した次の番地を拾ってくれるんだな。そりゃそうだ。実行して初めて エラーに気付くのは道理。実行する前に気付くなんて程、賢くはありません。
trap_undに飛び込んできた時、例外発生命令の次のアドレスはlr(リンク・レジスター)に保存 されてる。扱いはサブルーチンコールと一緒だな。動きが分かってくると面白いな。
gdb tui
ハロー"Hello World"本こと、ハロワ攻略本を買った。
雰囲気は、同業者の手の内を暴露する本。著者の方は、普段は FreeBSD使いのはずなんだけど、Linuxの魔界に首を突っ込んだばかりに、この本が 生まれたと告白されてる。有り難い事です。
gdbで動きを追ってくってストーリーなんだけど、オイラーに取っては初見のコマンドが 公表されてて、得る所大。参考文献に挙がっていた、昔々のgdb解説書は、オイラーも 持ってるけど、そこには無いコマンド。
今回の件をトリガーにgdbコマンドを調べてみましたよ。 TUI固有のコマンド
そして、その成果。
+--Register group: general-----------------------------------------------------+ |r0 0x1 1 | |r1 0x3ff0 16368 | |r2 0xcfac 53164 | |r3 0x20000050 536870992 | |r4 0xcfac 53164 | |r5 0x0 0 | |r6 0x0 0 | |r7 0x0 0 | |r8 0x0 0 | |r9 0x0 0 | |r10 0x0 0 | |r11 0x3fd4 16340 | |r12 0x0 0 | |sp 0x3fc8 0x3fc8 | |lr 0x80 128 | |pc 0x5c 0x5c <sti+40> | |cpsr 0x20000050 536870992 | +---------------------------------------------------------------------------+ |0x48 <sti+20> ldr r3, [r11, #-8] | |0x4c <sti+24> bic r3, r3, #128 ; 0x80 | |0x50 <sti+28> str r3, [r11, #-8] | |0x54 <sti+32> ldr r3, [r11, #-8] | |0x58 <sti+36> msr CPSR_fsxc, r3 | >|0x5c <sti+40> sub sp, r11, #0 | |0x60 <sti+44> pop {r11} ; (ldr r11, [sp], #4) | |0x64 <sti+48> bx lr | |0x68 <main> push {r11, lr} | |0x6c <main+4> add r11, sp, #4 | |0x70 <main+8> sub sp, sp, #16 | +---------------------------------------------------------------------------+ remote Thread 1 In: sti Line: 26 PC: 0x5c sti () at test.c:19 (gdb) info win ASM (13 lines) <has focus> REGS (19 lines) CMD (6 lines) (gdb)
これは良い。こういう設定を経て、上記画面分割が得られました。
layout asm tui reg general winheight reg 19
上記は、レジスターを常に表示してるけど、それは止めて、layout splitして、srcとasmを 同時表示でもいいかも。たまにregisterの値を見たかったら、p/x $lrとかしてもいいし、 CTL-x a してから、info registersでもよい。CTL-x aは、TUIモードに入ったり出たり(標準モード) する切り替えスイッチになってる。
また、gdbserverってのは、ネットでも良くみたんですけど、そのパワーを知らず、宝の 持ち腐れになってました。攻略本にはしっかり説明が載ってて、知見が拡がりました。 GDB/GDBserverによるクロスターゲットのリモートデバッグ
地方に居ると、秋葉原とか神保町とか、渋谷で行われている、同業者の会合にも気軽に 出る事が出来ず、つまらないです。こういうまとまった資料が有ると大いに助かります。
ああ、そうだ。渋谷の集まり(Shibuya.lisp)もメモしておこう。 Lisp Meet Up