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同盟に入ってるしね。