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が満たされるまでループします。
残った配列の要素を0で初期化する
for(; i < 10; i++) pcs[i] = 0;
1つ目のfor文の処理で中断した場合は、残った配列の要素を0の値で初期化します。
1つ目のfor文の処理で配列が満たされるまで継続した場合は、2つ目のfor文の処理に入らず終了します。