spinlock.c void getcallerpcs(void *v, uint pcs[])

トップページ
jupiteroak.hatenablog.com


spinlock.c
https://github.com/mit-pdos/xv6-public/blob/master/spinlock.c#L71

void getcallerpcs(void *v, uint pcs[])
{
  uint *ebp;
  int i;

  ebp = (uint*)v - 2;
  for(i = 0; i < 10; i++){
    if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff)
      break;
    pcs[i] = ebp[1];     // saved %eip
    ebp = (uint*)ebp[0]; // saved %ebp
  }
  for(; i < 10; i++)
    pcs[i] = 0;
}

getcallerpcs関数は、今まで呼び出された各関数で使用されているコールスタックから、リターンアドレスを取得し、ロック(spinlock構造体のpcsメンバ)に記録します(デバッグのためにスタックトレースを記録します)。

引数 void *v
コールスタックの先頭アドレスを取得するために使用されるアドレスです。

引数 uint pcs[]
今まで呼び出された各関数のコールスタックにあるリターンアドレスを格納する配列です。

処理の内容

コールスタックの先頭アドレスを取得する

ebp = (uint*)v - 2;

引数で渡されたアドレスvから逆算して、コールスタックの先頭アドレスを取得しています。
例えば、kfree関数→aquire関数→getcallerpcs関数の順で呼び出されていた場合、各関数で使用されているコールスタックは以下のような状態になるので、v-2(&lk-2)は、acquire関数が使うコールスタックの先頭アドレスとなります。

アドレス値 メモリ領域
下位側
... ...
getcallerpcs関数が使うコールスタックの先頭アドレス acquire関数が使うコールスタックの先頭アドレスの値
acquire関数が使うコールスタックの終端アドレス リターンアドレスの値
getcallerpcs関数の第一引数の値v(&lk)
getcallerpcs関数の第二引数の値pcs[](lk->pcs)
acquire関数が使うコールスタックの先頭アドレス &lk-2 kfree関数が使うコールスタックの先頭アドレスの値
kfree関数が使うコールスタックの終端アドレス リターンアドレスの値
&lk acquireの引数lk(&kmem.lock)
... ...
kfree関数が使うコールスタックの先頭アドレス 呼び出し元関数が使うコールスタックの先頭アドレスの値
上位側

今まで呼び出された関数のコールスタックからリターンアドレスを取得する

前の処理で取得したコールスタックの先頭アドレスebpを起点にして、今まで呼び出された関数のコールスタックからリターンアドレスを取得し、配列pcsに保存します。

コールスタックの構造について

「呼び出し先関数が使うコールスタックの先頭アドレスが指定するメモリ」には「呼び出し元関数が使うコールスタックの先頭アドレスの値」が格納されています。また、「呼び出し先関数が使うコールスタックの先頭アドレスが指定するメモリ」は、「呼び出し元関数が使うコールスタックの終端アドレスが指定するメモリ」と隣接しており、「呼び出し元関数が使うコールスタックの終端アドレスが指定するメモリ」には「リターンアドレスの値」が格納されています。

アドレス値 メモリ領域
下位側
... ...
呼び出し先関数が使うコールスタックの先頭アドレス 呼び出し元関数が使うコールスタックの先頭アドレスの値
呼び出し元関数が使うコールスタックの終端アドレス リターンアドレスの値
...
...
呼び出し元関数が使うコールスタックの先頭アドレス   
上位側

そのため、「呼び出し先関数が使うコールスタックの先頭アドレスの指定するメモリ」がebp[0]となる時、「呼び出し元関数が使うコールスタックの終端アドレスの指定するメモリ」はebp[1]となり、ebp[1]に「リターンアドレスの値」が格納されていることになります。

アドレス値 メモリ領域
下位側
... ...
呼び出し先関数が使うコールスタックの先頭アドレス ebp[0] 呼び出し元関数が使うコールスタックの先頭アドレスの値
呼び出し元関数が使うコールスタックの終端アドレス ebp[1] リターンアドレスの値
...
...
呼び出し元関数が使うコールスタックの先頭アドレス   
上位側

例えば、acquire関数が使うコールスタックの先頭アドレスの値をebpに代入した場合、
acquire関数が使うコールスタックの先頭アドレスが指定するメモリebp[0]には、kfree関数が使うコールスタックの先頭アドレスの値が格納されていることになります(kfree関数が呼び出し元関数となる場合)。
そして、kfree関数が使うコールスタックの先頭アドレスの値をebpに代入した場合、
関数が使うコールスタックの先頭アドレスが指定するメモリebp[0]には、freevm関数が使うコールスタックの先頭アドレスの値が格納されていることになります(freevm関数が呼び出し元関数となる場合)。

アドレス値 メモリ領域
下位側
... ...
acquire関数が使うコールスタックの先頭アドレス kfree関数が使うコールスタックの先頭アドレスの値
kfree関数が使うコールスタックの終端アドレス リターンアドレスの値
...
kfree関数が使うコールスタックの先頭アドレス    freevm関数が使うコールスタックの先頭アドレスの値
freevm関数が使うコールスタックの終端アドレス    リターンアドレスの値
...
freevm関数が使うコールスタックの先頭アドレス   
上位側

このような繰り返しで、今までに呼び出された関数のコールスタックにアクセスしながら、リターンアドレスを取得していきます。

継続条件について

if(ebp == 0 || ebp < (uint*)KERNBASE || ebp == (uint*)0xffffffff)
      break;

ebpの値(ある関数が使うコールスタックの先頭アドレスの値)が、0になっている(ebp == 0)、または、ユーザー空間のアドレスになっている(ebp < (uint*)KERNBASE)、または、メモリ空間の終端アドレスになっている(ebp == (uint*)0xffffffff)場合は、処理を中断してループを脱出します。そうではない場合は配列pcsが満たされるまでループします。

リターンアドレスを取得する

pcs[i] = ebp[1];     // saved %eip

ebp[1](呼び出し元関数が使うコールスタックの終端アドレスが指定するメモリ)から「リターンアドレス」を取得し、pcs配列に格納します。
「呼び出し先関数が使うコールスタックの先頭アドレスが指定するメモリebp[0]」は、「呼び出し元関数が使うコールスタックの終端アドレスが指定するメモリebp[1]」と隣接しており、「呼び出し元関数が使うコールスタックの終端アドレスが指定するメモリebp[1]」には「リターンアドレスの値」が格納されています。

ebpを更新する

ebp = (uint*)ebp[0]; // saved %ebp

ebp[0](呼び出し先関数が使うコールスタックの先頭アドレスが指定するメモリ)から「呼び出し元関数が使うコールスタックの先頭アドレスの値」を取得し、ebpを更新します。
「呼び出し先関数が使うコールスタックの先頭アドレスが指定するメモリebp[0]」には「呼び出し元関数が使うコールスタックの先頭アドレスの値」が格納されています。

残った配列の要素を0で初期化する

for(; i < 10; i++)
    pcs[i] = 0;

1つ目のfor文の処理で中断した場合は、残った配列の要素を0の値で初期化します。
1つ目のfor文の処理で配列が満たされるまで継続した場合は、2つ目のfor文の処理に入らず終了します。