Linuxカーネルのprintk()はformat stringが色々と拡張されていて(Documentation/printk-formats.txt)、例えば、IPv4/IPv6の表示用、UUID/GUIDの表示用などがあります。その中でsymbol関連のformat stringもいくつか合って、%pSの場合はこんなふうに呼び出し元関数、呼び出し位置/関数サイズというように表示できます。OOPSでのスタックトレースと同様ですね。
pr_info("%pS\n", __builtin_return_address(1));
出力結果はこのように。
[ 53.483834] load_module+0x1dcc/0x25f0
これはkallsymsの機能を使っているのだけど、どのようにやっているのかを見るのが今回の目的。 基本的にはlib/vsprintf.cのvsprintf()から関数が呼ばれていって、symbol_string()からkernel/kallsyms.cにあるsprint_symbol()が呼ばれ、あとはkallsyms.cの関数が使われていく。
symbolのlookupはkallsyms_lookup()が使われる。関数のサイズやoffset等もここで取っている模様。
355 /* Look up a kernel symbol and return it in a text buffer. */ 356 static int __sprint_symbol(char *buffer, unsigned long address, 357 int symbol_offset, int add_offset) 358 { 359 char *modname; 360 const char *name; 361 unsigned long offset, size; 362 int len; 363 364 address += symbol_offset; 365 name = kallsyms_lookup(address, &size, &offset, &modname, buffer); 366 if (!name) 367 return sprintf(buffer, "0x%lx", address);
kallsyms_lookup()はこのようになっていて、対象のアドレスがvmlinux内に存在するのか、カーネルモジュールにあるのかで使う関数が変わる。
292 const char *kallsyms_lookup(unsigned long addr, 293 unsigned long *symbolsize, 294 unsigned long *offset, 295 char **modname, char *namebuf) 296 { 297 namebuf[KSYM_NAME_LEN - 1] = 0; 298 namebuf[0] = 0; 299 300 if (is_ksym_addr(addr)) { 301 unsigned long pos; 302 303 pos = get_symbol_pos(addr, symbolsize, offset); 304 /* Grab name */ 305 kallsyms_expand_symbol(get_symbol_offset(pos), 306 namebuf, KSYM_NAME_LEN); 307 if (modname) 308 *modname = NULL; 309 return namebuf; 310 } 311 312 /* See if it's in a module. */ 313 return module_address_lookup(addr, symbolsize, offset, modname, 314 namebuf); 315 }
is_ksym_addr()はaddrがstext・endの範囲内にあるか、もしくはVSYSCALL_START・VSYSCALL_END内にあるかをチェックして、範囲内なら1を返す関数。ここで使うstext、endはカーネルビルド時にarch/x86/kernel/vmlinux.ldsに書かれてます。
今回はis_ksym_addr()が1を返した場合だけ見てきましょう。まずはget_symbol_pos()でsymbolを探します。
シンボルをどこから探しているのかというと、kallsyms_addresses[]から。
35 36 /* 37 * These will be re-linked against their real values 38 * during the second link stage. 39 */ 40 extern const unsigned long kallsyms_addresses[] __weak; 41 extern const u8 kallsyms_names[] __weak;
この配列からアドレスを探すのだけど、さすがに先頭から舐めていくのは時間がかかるのでバイナリサーチが使われます。バイナリサーチして配列のインデックスを見つけたら、シンボルにはエイリアスが使われることもあるようなので、そのインデックスから下方向に同じアドレスを使うものがあるか見ていき、見つかったところにあるアドレスがsymbolの開始アドレス(symbol_start)となります。
237 /* 238 * Search for the first aliased symbol. Aliased 239 * symbols are symbols with the same address. 240 */ 241 while (low && kallsyms_addresses[low-1] == kallsyms_addresses[low]) 242 --low;
次は、エイリアスじゃないアドレス(別のsymbol)の位置を探します。この場合、別のsymbolが見つかった場合はそのアドレスになるし、見つからなかった場合は_endや、テキストセクションの終端のアドレスが設定されます。これはsymbol_endという変数が使われます。
ここまででsymbolの開始・終了位置が分かりました。 次の処理でsymbolのサイズを計算します。これは単に終了アドレスから開始アドレスを引くだけですね。
264 if (symbolsize) 265 *symbolsize = symbol_end - symbol_start;
その次はoffsetなので関数の呼び出し位置ですかね。対象になっているアドレスから開始位置を引くことで、開始位置から何バイト目で実行されたかが分かると。
266 if (offset) 267 *offset = addr - symbol_start;
最後にkallsyms_addresses[]のインデックスとして見つけたlowを返して終了です。
get_symbol_pos()が終わると次はkallsyms_expand_symbol()でsymbol名の取得処理ですね。 まず、第一引数はget_symbol_offset()の戻り値でこれはkallsyms_expand_symbol()で使用するためのインデックスを決定しています。
名称はkallsyms_names、kallsyms_token_table、kallsyms_token_indexの3つの配列から探します。
後者の2個はこのように定義。
50 extern const u8 kallsyms_token_table[] __weak; 51 extern const u16 kallsyms_token_index[] __weak;
まず、kallsyms_names[]からで、offはget_symbol_offset()の返り値。これで長さを取ってますね。
97 /* Get the compressed symbol length from the first symbol byte. */ 98 data = &kallsyms_names[off]; 99 len = *data; 100 data++;
次は名前を1byteずつresultにコピーしていく処理。テーブルの関連としてはkallsyms_token_tableに実際のsymbol名があって、そのテーブルを引くためにkallsyms_token_indexを使い、kallsyms_token_indexのテーブルを引くためにkallsyms_namesを使っていますね。
/* 109 * For every byte on the compressed symbol data, copy the table 110 * entry for that byte. 111 */ 112 while (len) { 113 tptr = &kallsyms_token_table[kallsyms_token_index[*data]]; 114 data++; 115 len--; 116 117 while (*tptr) { 118 if (skipped_first) { 119 if (maxlen <= 1) 120 goto tail; 121 *result = *tptr; 122 result++; 123 maxlen--; 124 } else 125 skipped_first = 1; 126 tptr++; 127 } 128 }
kallsyms_lookup()が終わると%pSに必要な情報、symbol名、offset、サイズが全部取れているのであどは__sprint_symbol()の方で文字列を組み立ててあげます。 うーん、symbol名を得るところはテーブル引きまくっているので、書く配列でのデータの持ち方がわからないと細かい部分はわからないですが、概要としてはこれで足りるかな。。
- 作者: Daniel P. Bovet,Marco Cesati,高橋浩和,杉田由美子,清水正明,高杉昌督,平松雅巳,安井隆宏
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2007/02/26
- メディア: 大型本
- 購入: 9人 クリック: 269回
- この商品を含むブログ (71件) を見る