ウォンツテック

そでやまのーと

OS作成 - ファイルシステム編

 よくわからないバグに遭遇。
関数定義だけはしているが他から一切呼ばれていない関数がコンパイル後に悪さをしている。。
具体的に言うと以下が問題の部分。

PUBLIC void __change_parentdir(ext3_dentry* parent, ext3_dentry* child)         
{                                                                               
  char* p = parent->d_dirblock;                                                 
  char* prev = p;                                                               
  u_int16_t len, sumlen;                                                        
  len = sumlen = 0;                                                             
                                                                                
  while (sumlen < BLOCK_SIZE) {                                                 
    prev = p;                                                                   
    len = ((u_int16_t*)(p+4))[0];                                               
    p += len;                                                                   
    sumlen += len;                                                              
  } 
}

という関数があり、この関数自体はどこからも呼ばれていないのにコンパイルするとkernelがbootm.binあたりの動作で停止している。さらに細かくみていくと

    len = ((u_int16_t*)(p+4))[0]; 

のみをコメントアウトすると正常にいくのでどうやらこれが悪さをしている模様。
どうゆうことだろう。。ということでこれをobjdumpで逆アセンブルした所をみてみる

    390f:       55                      push   %ebp
    3910:       89 e5                   mov    %esp,%ebp
    3912:       83 ec 10                sub    $0x10,%esp
    3915:       8b 45 08                mov    0x8(%ebp),%eax
    3918:       8b 40 30                mov    0x30(%eax),%eax
    391b:       89 45 f4                mov    %eax,0xfffffff4(%ebp)
    391e:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
    3921:       89 45 f8                mov    %eax,0xfffffff8(%ebp)
    3924:       66 c7 45 fe 00 00       movw   $0x0,0xfffffffe(%ebp)
    392a:       0f b7 45 fe             movzwl 0xfffffffe(%ebp),%eax
    392e:       66 89 45 fc             mov    %ax,0xfffffffc(%ebp)
    3932:       eb 22                   jmp    0x3956
    3934:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
    3937:       89 45 f8                mov    %eax,0xfffffff8(%ebp)
    393a:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
    393d:       83 c0 04                add    $0x4,%eax
    3940:       0f b7 00                movzwl (%eax),%eax
    3943:       66 89 45 fc             mov    %ax,0xfffffffc(%ebp)
    3947:       0f b7 45 fc             movzwl 0xfffffffc(%ebp),%eax
    394b:       01 45 f4                add    %eax,0xfffffff4(%ebp)
    394e:       0f b7 45 fc             movzwl 0xfffffffc(%ebp),%eax
    3952:       66 01 45 fe             add    %ax,0xfffffffe(%ebp)
    3956:       66 81 7d fe ff 0f       cmpw   $0xfff,0xfffffffe(%ebp)
    395c:       76 d6                   jbe    0x3934
    395e:       c9                      leave
    395f:       c3                      ret

ここでespはスタックポインタでebpにそれをコピーしており、espから0x10を引くことにより16バイト分のローカル変数領域を確保している。0xfffffffe(%ebp)などはgccがローカル変数としてスタック上に用意したものでローカル変数の詳しい対応を見てみると

char* p char* prev u_int16_t len u_int16_t sumlen
-12(%ebp) -8(%ebp) -4(%ebp) -2(%ebp)
0xfffffff4(%ebp) 0xfffffff8(%ebp) 0xfffffffc(%ebp) 0xfffffffe(%ebp)

となってる、これより問題の1行は

    393a:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
    393d:       83 c0 04                add    $0x4,%eax
    3940:       0f b7 00                movzwl (%eax),%eax
    3943:       66 89 45 fc             mov    %ax,0xfffffffc(%ebp)

となってる。
pのアドレスを0xfffffff4(%ebp)から%eaxにコピーし、add $0x4, %eaxでp+4とし、それをメモリ上のアドレスとした値をもう一度%eaxにコピーし、最後に0xfffffffc(%ebp)であるlenにコピーする。

うーん。。問題無さそう。カーネルはメモリ上の0x1000から配置しているのでカーネルの容量増大が0x90000に配置したbootm.binを圧迫してるのかとも思ったけど、それならbootm.bin自体のコードが全く動かないはずだけどそれはないし、カーネル自体のサイズもまだ0x3C000しかないから問題ない。
あとはスタックの関係だろうか。。

追記 問題の箇所が違った

もっとコメントアウトしないと正常動作しない

追記

以下のコードを同じファイルに入れただけでおかしくなった

void hoge()
{
  int i = 0;
  while (i < 4096) {
    i++;
  }
}

どうやらアセンブラ的に「jmp」するコードが入るとおかしくなる。jmp先のアドレスが正常にリンクされてなさそう。

追記

どうやらkernel.binのサイズが191800Byte以上になるとメモリ上の0x9036bから0x90374までが上書きされてそこから挙動がおかしくなっている模様。なぜそんな事になってるかはまだ不明。
根が深いバグだ。。なんでこんな所のしかも10バイトだけが上書きされるんだ。。

メモリ上のダンプ

カーネルバイナリ上のダンプ


違う配列に見えるのはエンディアンの表示方法の差