Xv6 (3)
遅ればせながら、 MAKERS―21世紀の産業革命が始まる なんて本を読んだ。勿論、図書館から借りてきたんだけど、おいらが一番手だった。みんなこういうのに 興味は無いんでしょうか? なんでも、ホームセンターや山田さん家へ行けば手に入る昨今、 尼通販って強力な手もあるからなあ。
著者は、ソフトウェア(主としてWeb)とハードウェアの世界をBit と Atom と言う言葉で 言い換えている。Bitの世界はOSSで一気に拡大したように、Atomの世界も同様な事が近い 将来起こると予測してる。
パソコンがガレージから生まれたのは有名な話だけど、ガレージに備え付けた3Dプリンタを 使って、大企業が作らなかったようなニッチな(ハードウェア)製品が、どんどん生まれて くるだろうってね。ああ、ハードウェアって言うとパソコンって言う頭しか無いおいらも 方向転換が必要だな。ここで言うハードウェアってのは、いわゆる実体って意味ね。
ソフトウェアを勉強する時は、コードをコピーしてそれを改造ってのが王道だけど、ハードウェアも 同様。写真をバチバチ撮って、それをコンピュータの中で解析して、3Dプリンターに理解出来る ようなデータ(Gコードと言うそうな)に変換し、必要なら改造を加えてから、プリンターに 出力する。
ハードウェア版の HelloWorldは、何故か、自分の頭部模型らしい。自分の分身の一部を 外からリアルに眺められるってのは、奇妙な感動を生むんだろうな。想像しただけで、ワクワク するよ。
肝心の3Dプリンタだけど、オープンハードウェアで作られているとか。世界中から部品を 調達して、少量生産してくれるメーカーに発注するらしい。そのうちに、エプソンとかHPとかの プリンターメーカーも、潜在需要に気が付いて、家庭用3Dプリンターを売り出すに違いない。
インクの代わりに、チョコレートを使って、自分のハートを作りプレゼントする、なんて事が 来年のバレンタインデーに行われたりして。ああ、バレンタインはチョコレート屋の陰謀だと 思っていたら、。 メモリーカード屋も加担してるな。
それじゃ、歴女みたいに、全国の仏像を出力するとか、あの人みたいに、狛犬を出力して 自分だけのコレクションを作るのも楽しそうだな。こういう時は、熱で熔けてしまわない ように、発砲スチロールあたりを素材にするのが良かろう。
.gdbinit
この話題の初回でgdbを動かしたとき、こんなのが出てた。
The target architecture is assumed to be i8086 [f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b : The target architecture is assumed to be i386 => 0x80100b08 <exec>: push %ebp
i8086がターゲットだよと言ったり、今度はi386だよと言ったり。逆アセンブルした時の アドレス表現方法もターゲットによって微妙に違う。こういう事はgdbが持って生まれた 技能なんだろうか? ふと、疑問に思った訳だ。普通にgdb使ってても、こんなメッセージ は見た事無いぞ。
見た事無い事をやるのは、gdbのユーザー拡張だな。そう思って、.gdbinitを見ると (.gdbinitはxv6の中に入っています、って言うか.gdbinit.tmplから作成される)
define hook-stop # There doesn't seem to be a good way to detect if we're in 16- or # 32-bit mode, but in 32-bit mode we always run with CS == 8 in the # kernel and CS == 35 in user space if $cs == 8 || $cs == 35 if $lastcs != 8 && $lastcs != 35 set architecture i386 end x/i $pc else if $lastcs == -1 || $lastcs == 8 || $lastcs == 35 set architecture i8086 end # Translate the segment:offset into a physical address printf "[%4x:%4x] ", $cs, $eip x/i $cs*16+$eip end set $lastcs = $cs end
こんな定義がされている。hook-stopって事は、gdbがbpとかに達して止まった時に実行されるんだな。 そして、その時のCSの値によりリアルモード(i8086)かプロテクトモード(i386)かを判定してんのか。 MITは芸が細かいな。
CSが8だとか35だとかは、xv6の事情だけど、もっと一般化するとなると、cr0のLSBを調べれば いいのかな。あれ? cr0とかgdtrみたいなハードウェア寄りのレジスターってのはgdbからも読めるのかな?
p/x $cr0 $3 = Value can't be converted to integer. p/x $cs $4 = 0x8
ダメだわ。やんわりと怒られた。まあ、こういうやつはOS屋さんしか触らんから未サポートでも しょうがないか。QEMUのモニターモードで、all-registerとかしてくださいってね。
Userlandをdebug
上で、CSが35になってる時はユーザーランドだからねって書いてあるけど、ユーザーランドは どうやってdebugするん? ちと考えちゃったぞ。そして方法を思い付いたぞ。
(gdb) i r eax 0x0 0 ecx 0x8010ff20 -2146369760 edx 0x24 36 ebx 0x10074 65652 esp 0x8010c5e0 0x8010c5e0 <stack+3984> ebp 0x8010c608 0x8010c608 <stack+4024> esi 0x10074 65652 edi 0x0 0 eip 0x80104737 0x80104737 <scheduler+122> eflags 0x93 [ CF AF SF ] cs 0x8 8 ss 0x10 16 ds 0x10 16 es 0x10 16 fs 0x18 24 gs 0x18 24 (gdb) she objdump -f _ls _ls: file format elf32-i386 architecture: i386, flags 0x00000012: EXEC_P, HAS_SYMS start address 0x00000304 (gdb) b *0x304 Breakpoint 1 at 0x304 (gdb) symbol-file /home/sakae/xv6/_ls Load new symbol table from "/home/sakae/xv6/_ls"? (y or n) y Reading symbols from /home/sakae/xv6/_ls...done.
eipとかcsを見ると、カーネルモードに居ます。これから、ユーザーランドの ls を調べたいので 、_lsのスタートアドレスを調べ、そこにPBを設定しました。ついでに、シンボルテーブルも取り寄せて おきます。これで、ソースレベルでdebug出来ます。 なお、_lsは、xv6上のlsコマンドです。
こうしておいて、xv6上のshellからlsすると
(gdb) c Continuing. => 0x304 <main>: push %ebp Breakpoint 1, main (argc=1, argv=0x2ff4) at ls.c:75 75 { (gdb) l 70 close(fd); 71 } 72 73 int 74 main(int argc, char *argv[]) 75 { 76 int i; 77 78 if(argc < 2){ 79 ls("."); (gdb) i r eax 0x0 0 ecx 0x8 8 edx 0xbfac 49068 ebx 0x0 0 esp 0x2fe8 0x2fe8 ebp 0x3fb8 0x3fb8 esi 0x0 0 edi 0x0 0 eip 0x304 0x304 <main> eflags 0x206 [ PF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0
見事に、ユーザーランド側で止まりました。後は普通にdebug出来ます。
=> 0x27c <ls+460>: mov -0x234(%ebp),%edi 66 printf(1, "%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size)
$ ls . 1 1 512 .. 1 1 512 README 2 2 2507 :
普通のlsと違って、余分な情報が出てきていたんだけど、gdbでソース追って、初めてその意味を 知りましたよ。(って、何だかわざとらしいぞ)
上記では、BPをセットするのに面倒な事をしたけど、下記の方法が手軽だね。
(gdb) symbol-file _cat Load new symbol table from "/home/sakae/xv6/_cat"? (y or n) y Reading symbols from /home/sakae/xv6/_cat...done. (gdb) b main Breakpoint 3 at 0x68: file cat.c, line 22. (gdb) c Continuing. => 0x68 <main>: push %ebp Breakpoint 3, main (argc=2, argv=0x2fe8) at cat.c:22 22 { (gdb) n => 0x71 <main+9>: cmpl $0x1,0x8(%ebp) 25 if(argc <= 1){ (gdb) p argv[1] $2 = 0x2ff4 "README"
但し、事前にlsのmainにセットしたBPをクリアしておく事。そうしないと、文句言われる。
kernelの情報を覗く
先ほどからgdbの実習が続いているので、引き続きやってみます。今度は、kernelが保持してる 種々の情報を覗いてみます。
まずは、どんな情報が取れるか、gdb上でshe(ll)を動かし、聞いてみます。
(gdb) she nm kernel | grep B 8010c660 B bcache 8010f920 B cpus 8010e800 B devsw 801126fc B end 8010de60 B ftable 801126c0 B gdt 8010e860 B icache 80111ea0 B idt 8010dda0 B input 8010f834 B ioapic 8010f900 B ioapicid 8010f904 B ismp 8010f840 B kmem 801126f8 B kpgdir 8010f87c B lapic 8010f880 B log 8010ff00 B ncpu 8010ff20 B ptable 8010b650 B stack 801126a0 B ticks 80111e60 B tickslock
nmを使って、BSSに登録されてる変数をリストしました。kmemなんて面白そうなので参照してみます。
(gdb) p/x kmem $8 = {lock = {locked = 0x0, name = 0x801082da, cpu = 0x0, pcs = {0x0, 0x80107eab, 0x80104628, 0x80105fc6, 0x80105208, 0x801063b2, 0x8010619d, 0x0, 0x0, 0x0}}, use_lock = 0x1, freelist = 0x8deeb000} (gdb) set print pretty on (gdb) p/x kmem $10 = { lock = { locked = 0x0, name = 0x801082da, cpu = 0x0, pcs = {0x0, 0x80107eab, 0x80104628, 0x80105fc6, 0x80105208, 0x801063b2, 0x8010619d, 0x0, 0x0, 0x0} }, use_lock = 0x1, freelist = 0x8deeb000 }
最初の表示は、ちと汚かったので、整形してみました。 ふむ、ソースの定義と見比べてみます。
struct spinlock { uint locked; // Is the lock held? // For debugging: char *name; // Name of lock. struct cpu *cpu; // The cpu holding the lock. uint pcs[10]; // The call stack (an array of program counters) // that locked the lock. }; struct run { struct run *next; }; struct { struct spinlock lock; int use_lock; struct run *freelist; } kmem;
単純ははずのxv6でも、こういう複雑なデータを保持してんだから、本物のOSだと目が眩む 程になってるんだろうな。
懲りずに、もう一つ。今度は、ptableを観察してみます。
$2 = { lock = { locked = 0x1, name = 0x801083d1, cpu = 0x8010f920, pcs = {0x801046d4, 0x80103568, 0x80103507, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }, proc = {{ sz = 0x3000, pgdir = 0x8dfbb000, kstack = 0x8dfff000, state = 0x2, pid = 0x1, parent = 0x0, tf = 0x8dffffb4, context = 0x8dfffe8c, chan = 0x8010ff54, killed = 0x0, ofile = {0x8010de94, 0x8010de94, 0x8010de94, 0x0 <repeats 13 times>}, cwd = 0x8010e894, name = {0x69, 0x6e, 0x69, 0x74, 0x0, 0x0, 0x64, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }, { sz = 0x4000, pgdir = 0x8df73000, kstack = 0x8dffe000, state = 0x2, pid = 0x2, parent = 0x8010ff54, tf = 0x8dffefb4, context = 0x8dffee0c, chan = 0x8010de54, killed = 0x0, ofile = {0x8010de94, 0x8010de94, 0x8010de94, 0x0 <repeats 13 times>}, cwd = 0x8010e894, name = {0x73, 0x68, 0x0 <repeats 14 times>} }, { sz = 0x0, : name = {0x0 <repeats 16 times>} } <repeats 62 times>} }
これ、xv6を起動して、shのプロンプトが出た時の状況を魚拓したものです。pidが1なのはinitで、 2はshになってるのが見て取れます。
一応、ソースの定義も見とくか。
struct proc { uint sz; // Size of process memory (bytes) pde_t* pgdir; // Page table char *kstack; // Bottom of kernel stack for this process enum procstate state; // Process state volatile int pid; // Process ID struct proc *parent; // Parent process struct trapframe *tf; // Trap frame for current syscall struct context *context; // swtch() here to run process void *chan; // If non-zero, sleeping on chan int killed; // If non-zero, have been killed struct file *ofile[NOFILE]; // Open files struct inode *cwd; // Current directory char name[16]; // Process name (debugging) }; struct { struct spinlock lock; struct proc proc[NPROC]; } ptable;
大事なデータには必ずロックを付けるのは、リアルワールドもコンピュータワールドも一緒の事です。
ソースの追っかけ
ストーカーします。ねちねち追いかけます。と、言ってもソースが対象だから、世間様に 迷惑をかけるでなし、思う存分やってくだせえ。
で、追跡用の道具なんだけど、xv6のMakefileを覗いてたら、
tags: $(OBJS) entryother.S _init etags *.S *.c
こんなのが見つかった。rmsの昔の住処だけあって、emacs使いがはこびっているのね。おいらも あやかろうって事で、tagを打っときました。
emacs上でtagの有効活用はどうするんだ? 知らない事は知るチャンスとばかり調べてみましたよ。 使い方は簡単でした。
調べたい単語に照準を合せて、M-. すると、定義先へ連れてってくれます。
元に戻るには、M-* です。これで、ソースの海をスーイ、スーイできます。
クリック猿な人は、gtagsしてからhtagsして、ブラウザーから漂うってのもいいかも知れません。 globalはGNU同盟に入ってるしね。