CScope
Linux Kernelのコードを読んでみようと思ったものの、さすがにgrepのみで読んでいくのはつらそうなので何かツールを探してみたところ、CScopeというツールがありました。
CScopeは、Cで書かれたソースコードからDBを作成し、それをVimやEmacsから利用することで、関数の定義や呼び出し元にジャンプできるナビゲーションツールです。大きなCのソースコードを読むのに便利そうなので、試してみました。
インストール
$ apt-get install cscope
DBを作成する
対象となるCのソースコードから、DBを作成します。DB用のディレクトリを用意します。
$ mkdir ~/cscope $ cd ~/cscope
次に、ソースコードをスキャンしてファイルの一覧を作り、DBファイルを作成します。コマンドが結構長いので、簡単なシェルスクリプトを作って実行しました。
#!/bin/sh LNX=/usr/src/linux-2.6.32.12 find $LNX \ -path "$LNX/arch/*" ! -path "$LNX/arch/i386*" -prune -o \ -path "$LNX/include/asm-*" ! -path "$LNX/include/asm-i386*" -prune -o \ -path "$LNX/tmp*" -prune -o \ -path "$LNX/Documentation*" -prune -o \ -path "$LNX/scripts*" -prune -o \ -path "$LNX/drivers*" -prune -o \ -name "*.[chxsS]" -print >/home/masayuki/cscope/cscope.files cscope -b -q -k
cscopeコマンドでDBファイルを生成するのですが、ソースファイル一覧(cscope.files)のパスが指定されてないですね…。DBは作成されていたので、デフォルトでカレント直下のcsope.filesを見てくれるのでしょう。
$ vi create_ksrc_index.sh $ chmod 755 create_ksrc_index.sh $ ./create_ksrc_index.sh $ ls -l ~/cscope 合計 121816 -rwxr-xr-x 1 masayuki masayuki 564 2010-05-11 02:24 create_ksrc_index.sh -rw-r--r-- 1 masayuki masayuki 460661 2010-05-11 02:24 cscope.files -rw-r--r-- 1 masayuki masayuki 8609792 2010-05-11 02:25 cscope.in.out -rw-r--r-- 1 masayuki masayuki 68566467 2010-05-11 02:25 cscope.out -rw-r--r-- 1 masayuki masayuki 46949052 2010-05-11 02:25 cscope.po.out
DBができました。
EmacsからDBを参照する
VimについてはCscopeのTutorialのページに使い方が載っています。僕はソースを読む際にEmacsを使いたいので、Emacs用のセッティングを行います。
$ ls -l /usr/share/emacs/site-lisp/xcscope.el
インストール時に「xcscope.el」がsite-lispの下に置かれるので、~/.emacsの設定を行えばCScopeを使うことができます。
$ vi ~/.emacs
(require 'xcscope) (setq cscope-database-regexps '( ("^/usr/src/linux-2.6.32.12" ("/home/masayuki/cscope") ) ) )
require以外では、「cscope-database-regexps」を設定をしています。これは、第1引数に対象となるソースコードのパスを正規表現を使って表したものを、第2引数には第1引数で指定したパスを基点にしたソースコードに対して適用するCScopeのDBファイルがあるディレクトリを記述します。つまり、第1引数のパスを基点とするファイルをEmacsで開いた場合、関数の定義やシンボル等を検索する際には第2引数で指定したDBが使われます。
準備ができたら、Emacsを起動します。
$ emacs
キーバインディング
xcscope.elにはデフォルトのキーバインディングが記載されています。
;; * Keybindings: ;; ;; All keybindings use the "C-c s" prefix, but are usable only while ;; editing a source file, or in the cscope results buffer: ;; ;; C-c s s Find symbol. ;; C-c s d Find global definition. ;; C-c s g Find global definition (alternate binding). ;; C-c s G Find global definition without prompting. ;; C-c s c Find functions calling a function. ;; C-c s C Find called functions (list functions called ;; from a function). ;; C-c s t Find text string. ;; C-c s e Find egrep pattern. ;; C-c s f Find a file. ;; C-c s i Find files #including a file. ;; ;; These pertain to navigation through the search results: ;; ;; C-c s b Display *cscope* buffer. ;; C-c s B Auto display *cscope* buffer toggle. ;; C-c s n Next symbol. ;; C-c s N Next file. ;; C-c s p Previous symbol. ;; C-c s P Previous file. ;; C-c s u Pop mark.
これらを使用して、ソースコードの任意の位置にジャンプします。
ナビゲーション
例えば、呼び出している関数のところでC-c s Gを実行すると、ソースコードの中でその関数を定義している箇所ににジャンプします。
[mm/swapfile.c]
offset = scan_base; spin_lock(&swap_lock); /* spin_lockの文字の上にキャレットを置いて、C-c s Gを実行 */ si->cluster_nr = SWAPFILE_CLUSTER - 1;
spin_lock関数はマクロでした。マクロでもちゃんと定義している箇所にジャンプします。
[include/linux/spinlock.h]
#define spin_lock(lock) _spin_lock(lock) #ifdef CONFIG_DEBUG_LOCK_ALLOC # define spin_lock_nested(lock, subclass) _spin_lock_nested(lock, subclass)
今度は_spin_lockのところでC-c s Gを実行します。cscopeバッファにいつくかの候補が出力されました。
Finding global definition: _spin_lock Database directory: /home/masayuki/cscope/ ------------------------------------------------------------------------------- *** /usr/src/linux-2.6.32.12/include/linux/spinlock_api_smp.h: _spin_lock[22] void __lockfunc _spin_lock(spinlock_t *lock) __acquires(lock); _spin_lock[79] #define _spin_lock(lock) __spin_lock(lock) *** /usr/src/linux-2.6.32.12/include/linux/spinlock_api_up.h: _spin_lock[51] #define _spin_lock(lock) __LOCK(lock) *** /usr/src/linux-2.6.32.12/kernel/spinlock.c: _spin_lock[136] void __lockfunc _spin_lock(spinlock_t *lock) -------------------------------------------------------------------------------
うーん、どれだろう。取り合えず実装を見てみたいので、kernel/spinlock.cを選びます。
[kernel/spinlock.c]
#ifndef _spin_lock =>id __lockfunc _spin_lock(spinlock_t *lock) { __spin_lock(lock); } EXPORT_SYMBOL(_spin_lock); #endif
やはり__spin_lockなのか。ということでさらにC-c s Gを実行。
[include/linux/spinlock_api_smp.h]
static inline void __spin_lock(spinlock_t *lock) { preempt_disable(); spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock); }
といった具合にどんどんソースコードを追って行くことができます。
反対に、任意の関数の呼び出し元を検索することもできます。上記の__spin_lockのところで、C-c s cを実行すると、cscopeバッファに以下のように表示されます。
Finding functions calling: __spin_lock Database directory: /home/masayuki/cscope/ ------------------------------------------------------------------------------- *** /usr/src/linux-2.6.32.12/include/linux/spinlock_api_smp.h: _spin_lock[79] #define _spin_lock(lock) __spin_lock(lock) *** /usr/src/linux-2.6.32.12/kernel/spinlock.c: _spin_lock[138] __spin_lock(lock); ------------------------------------------------------------------------------- Search complete. Search time = 5.69 seconds.
cscopeバッファに検索結果が表示されました。検索結果の中から一つを選択し、__spin_lock関数の呼び出し元にジャンプします。
これで何とかソースコードを追えそうな気がしてきました。