読者です 読者をやめる 読者になる 読者になる

昨日のoopsから,バグが発生した場所を調べます.

debug

昨日のoopsはこれです.

まず,oopsにある原因のコードの場所を見ます.

[12643.727359] RIP: 0010:[<ffffffffa02a00ae>]  [<ffffffffa02a00ae>] count_free+0xae/0x131 [minix]

そうすると,count_free()という関数で落ちていることがわかります.
後ろの0xaeはバグの場所で,0x131は関数のサイズです.
count_free()の0xaeバイト目のコードが落ちた場所ということになります.
count_free()はfs/minix/bitmap.cとmm/slub.cの2ヶ所にあるのですが,[minix]から,bitmap.cだろうと言う感じです.

そしたら,bitmap.oを調べます.使うのはobjdumpです.

[masami@moonlight:~/linux/.../fs/minix]% objdump -D bitmap.o | less

bitmap.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <count_free>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   41 56                   push   %r14
〜中略〜
  a4:   44 89 c1                mov    %r8d,%ecx
  a7:   48 8b 5f 28             mov    0x28(%rdi),%rbx
  ab:   41 ff c0                inc    %r8d
  ae:   8a 0c 0b                mov    (%rbx,%rcx,1),%cl
  b1:   88 cb                   mov    %cl,%bl
  b3:   83 e1 0f                and    $0xf,%ecx
  b6:   c0 eb 04                shr    $0x4,%bl
〜略〜

これで,オフセット0xaeのコードは「 mov (%rbx,%rcx,1),%cl」ということがわかりました.
mov (%rbx,%rcx,1),%clは,1rbxで示されるベース・アドレスの%rcx*1バイト目のデータを%clにmovすると言う意味です.

ちなみに,oopsのCodeで表示されているバイト列は,0xae前後のコードです.
この情報を元に,cで書かれたコードを探します.

bitmap.oで見ると,0xbdバイト目の00 00 00ですが,oopsで表示されている内容の最後3バイトは60 3f 2aです.
これは,リンク時に設定された値になっていると思います.

  bd:   03 04 9d 00 00 00 00    add    0x0(,%rbx,4),%eax

まずは,目印になる関数などを探すわけですが,その前にどんな関数か見ておきます.

static unsigned long count_free(struct buffer_head *map[], unsigned numblocks, __u32 numbits)
{
        unsigned i, j, sum = 0;
        struct buffer_head *bh;

        for (i=0; i<numblocks-1; i++) {
                if (!(bh=map[i]))
                        return(0);
                for (j=0; j<bh->b_size; j++)
                        sum += nibblemap[bh->b_data[j] & 0xf]
                                + nibblemap[(bh->b_data[j]>>4) & 0xf];
        }

        if (numblocks==0 || !(bh=map[numblocks-1]))
                return(0);
        i = ((numbits - (numblocks-1) * bh->b_size * 8) / 16) * 2;
        for (j=0; j<i; j++) {
                sum += nibblemap[bh->b_data[j] & 0xf]
                        + nibblemap[(bh->b_data[j]>>4) & 0xf];
        }

        i = numbits%16;
        if (i!=0) {
                i = *(__u16 *)(&bh->b_data[j]) | ~((1<<i) - 1);
                sum += nibblemap[i & 0xf] + nibblemap[(i>>4) & 0xf];
                sum += nibblemap[(i>>8) & 0xf] + nibblemap[(i>>12) & 0xf];
        }
        return(sum);
}

関数の呼び出しとかが無いので,あまり分かりやすい目印がないのですが・・・
この関数だと,return 0;してる箇所が2個あるので,2個目のreturn文を探しました.
この他にも,0xfとandを取っている部分も目印になります.

0を返すなら,xorでeaxを0にするところがあるだろうというのは想像がつくので,
これを探すと,アドレス0x126が,その部分ってわかります.

 124:   eb 02                   jmp    128 <count_free+0x128>
 126:   31 c0                   xor    %eax,%eax
 128:   5b                      pop    %rbx
 129:   41 5c                   pop    %r12
 12b:   41 5d                   pop    %r13
 12d:   41 5e                   pop    %r14
 12f:   c9                      leaveq 
 130:   c3                      retq   

そして,0x126に飛ぶ処理を探すと,0x82にあるje命令がまさにアドレス0x126を指定してます.

  72:   0f 84 ae 00 00 00       je     126 <count_free+0x126>
  78:   45 89 d8                mov    %r11d,%r8d
  7b:   4a 8b 3c c7             mov    (%rdi,%r8,8),%rdi
  7f:   48 85 ff                test   %rdi,%rdi
  82:   0f 84 9e 00 00 00       je     126 <count_free+0x126> <--- return 0を実行する部分
  88:   89 d6                   mov    %edx,%esi
  8a:   48 8b 4f 20             mov    0x20(%rdi),%rcx

落ちた場所は0xaeなので,このreturn文より後であることがわかります.

この後の処理で分かりやすいのは,j=0の部分なので,こいつを探します.

  8e:   48 c1 e1 03             shl    $0x3,%rcx
  92:   49 0f af c8             imul   %r8,%rcx
  96:   45 31 c0                xor    %r8d,%r8d             <--- j=0 	

そうすると,0x96にxor命令がいるので,こいつがj=0の実行箇所ですね.
inc命令もいるので,間違いなさそうです.

  99:   48 29 ce                sub    %rcx,%rsi
  9c:   48 c1 ee 04             shr    $0x4,%rsi
  a0:   01 f6                   add    %esi,%esi
  a2:   eb 27                   jmp    cb <count_free+0xcb>
  a4:   44 89 c1                mov    %r8d,%ecx
  a7:   48 8b 5f 28             mov    0x28(%rdi),%rbx
  ab:   41 ff c0                inc    %r8d                  <--- j++

inc命令の次が,例の0xaeです.

cのコードだとこの中のどれかです.

                sum += nibblemap[bh->b_data[j] & 0xf]
                        + nibblemap[(bh->b_data[j]>>4) & 0xf];

アセンブラの命令は「mov (%rbx,%rcx,1),%cl」です.
また, nibblemapはint型の配列です.nibblemapのオフセットを求めて,clレジスタに設定したいというのが,命令の内容のようです.

static const int nibblemap[] = { 4,3,3,2,3,2,2,1,3,2,2,1,2,1,1,0 };

命令に使ってるrcxレジスタはjの値です.これは0xa4でr8dレジスタの値を,ecxレジスタにコピーしているので簡単にわかります.

  99:   48 29 ce                sub    %rcx,%rsi
  9c:   48 c1 ee 04             shr    $0x4,%rsi
  a0:   01 f6                   add    %esi,%esi
  a2:   eb 27                   jmp    cb <count_free+0xcb>
  a4:   44 89 c1                mov    %r8d,%ecx
  a7:   48 8b 5f 28             mov    0x28(%rdi),%rbx
  ab:   41 ff c0                inc    %r8d                  <--- j++

では,rdxレジスタは?ということですが,これはbh->b_dataです.
rdxレジスタにbh->b_dataが設定されているのは以下の場所です.
ソースコードアセンブラで設定する位置が違いますが,これは最適化のためと思います.

  a7:   48 8b 5f 28             mov    0x28(%rdi),%rbx

struct buffer_headはinclude/linux/buffer_head.hで定義されている構造体です.

struct buffer_head {
        unsigned long b_state;          /* buffer state bitmap (see above) */
        struct buffer_head *b_this_page;/* circular list of page's buffers */
	struct page *b_page;            /* the page this bh is mapped to */

        sector_t b_blocknr;             /* start block number */
        size_t b_size;                  /* size of mapping */
        char *b_data;                   /* pointer to data within the page */

        struct block_device *b_bdev;
	bh_end_io_t *b_end_io;          /* I/O completion */
        void *b_private;                /* reserved for b_end_io */
	struct list_head b_assoc_buffers; /* associated with another mapping */
        struct address_space *b_assoc_map;      /* mapping this buffer is                                                                                    
                                                   associated with */
	atomic_t b_count;               /* users using this buffer_head */
};

ウチのPCは64bitなので,8バイトアラインで数えると構造体の先頭からb_dataまでは0x28(40)バイトです.
他の場所でb_sizeにアクセスしてる場所では,0x20がオフセットになっています.

  8a:   48 8b 4f 20             mov    0x20(%rdi),%rcx

そんな訳で,「mov (%rbx,%rcx,1),%cl」はbh->b_data[j]であることが分かりました(∩´∀`)∩ワーイ

  ae:   8a 0c 0b                mov    (%rbx,%rcx,1),%cl     <--- bh->b_data[j]
  b1:   88 cb                   mov    %cl,%bl
  b3:   83 e1 0f                and    $0xf,%ecx
  b6:   c0 eb 04                shr    $0x4,%bl
  b9:   48 0f be db             movsbq %bl,%rbx
  bd:   03 04 9d 00 00 00 00    add    0x0(,%rbx,4),%eax

これでバグの場所自体はわかったんですが,どういう状況だったのかは,テストケースとテストデータ調べないとわかりませんorz
なので,まだまだ直すところまではいけません・・・