CScope

Linux Kernelのコードを読んでみようと思ったものの、さすがにgrepのみで読んでいくのはつらそうなので何かツールを探してみたところ、CScopeというツールがありました。
CScopeは、Cで書かれたソースコードからDBを作成し、それをVimEmacsから利用することで、関数の定義や呼び出し元にジャンプできるナビゲーションツールです。大きな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関数の呼び出し元にジャンプします。
これで何とかソースコードを追えそうな気がしてきました。