Menelusuri
Kode Program dengan Cscope dan Ctags
Pernah
merasa pusing saat memprogram ribuan baris? Atau suatu hari ingin
menelusuri kode kernel yang jumlahnya sampai puluhan ribu baris?
Cscope dan Ctags bisa jadi solusinya.
Mulanya
saya juga berpikir “ribet sekali menelusuri kode program yang
bejibun ini.” Memang ini masalah klasik, dari jaman mainframe
sampai PC, dari jaman DOS sampai era Linux kernel 2.6. Sebenarnya ini
masalahnya serupa (tapi tak sama) bagi programmer, yakni bagaimana
menelusuri program secara cepat dan efisien. Proses yang tepat dalam
menelusuri program ini berpengaruh pada kecepatan development, juga
memudahkan penelusuran jika ditemukan bug. Memang kata "cepat"
ini sangat relatif. Ada yang mampu secara intuitif mencari letak
suatu procedure, ada yang mesti serius memelototi editor sambil tekan
tombol Page Up dan Page Down. Ada yang enjoy (ini kalau di Linux)
pake grep dan cat. Benar-benar banyak jalan menuju “Roma.”
Masalahnya
sekarang, bagaimana kalau kode program itu sangat banyak, semisal
kode kernel? Waktu saya mencoba menelusuri kode kernel Linux, saya
sering bertemu suatu struct (record, istilah di Pascal) yang
definisinya ada di file header lain. Ini masih belum seberapa. Ada
juga pemanggilan fungsi atau prosedur pada direktori yang berbeda,
dengan referensinya "tersembunyi" pada deklarasi header di
dalam file header lain (rekursif declaration). Lalu bagaimana?
Menurut saya ada dua cara, kuatkan mental anda atau gunakan duet
Cscope dan Ctags.
Sebagai
contoh kasus dalam artikel ini, saya akan mencoba menelusuri kode
kernel bawaan Red Hat 7.3 dengan editor Vim. Tools yang akan
digunakan adalah Cscope dan Ctags yang disambungkan (atau di-binding)
langsung dengan Vim. Oleh karena itu, kita langsung saja mencoba
tools ini. Pertama download Cscope dari http://cscope.sourceforge.net
di section Download. Ambil versi terbaru (saat tulisan ini dibuat)
yaitu versi 15.5. Penulis saat ini menggunakan versi 15.3 tapi cara
penggunaannya sama saja. Untuk Ctags, ambil versi terbaru (saat
artikel ini ditulis, versi 5.5.2) di http://ctags.sourceforge.net.
Letakkan
kedua file ini di suatu direktori, misal di /usr/src, lalu unpack
dengan perintah tar.
#
tar xzvf ./ctags-5.5.2
#
tar xzvf ./cscope-15.3.tar.gz
Segera
configure, kompilasi, dan instalasi.
#
cd cscope-15.3
#
./configure --prefix=/usr/local/cscope
#
make && make install
#
cd ../ctags-5.5.2
#
./configure --prefix=/usr/local/ctags
#
make && make install
Buat
symbolic link dari masing-masing executable ke /usr/local/bin agar
memudahkan eksekusi program.
#
ln -s /usr/local/cscope/bin/cscope /usr/local/bin/cscope
#
ln -s /usr/local/ctags/bin/ctags /usr/local/bin/ctags
Pindahkan
juga beberapa man file agar memudahkan jika nanti anda ingin membaca
setting cscope/ctags lebih lanjut.
#
mv /usr/local/ctags/man/man1/ctags.1 /usr/man/man1/
#
mv /usr/local/ctags/man/man1/etags.1 /usr/man/man1/
#
mv /usr/local/cscope/man/man1/cscope.1 /usr/man/man1/
Sekarang
kita fokus dulu ke Cscope. Langkah berikutnya yang harus kita lakukan
adalah membuat daftar file untuk di-index oleh Cscope. Langkah ini
tidak mutlak dilakukan, namun sangat berguna untuk mempercepat proses
pencarian. Untuk membuat daftar file di kernel tree, anda bisa
gunakan shell script berikut ini.
#!/bin/bash
LNX=/usr/src/linux-2.4/
cd
/
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 > /var/cscope/kernel/cscope.files
Variabel
LNX bisa anda ganti dengan sembarang path tempat anda meletakkan
source code kernel atau project anda. Jangan lupa tanda "/"
di akhir path. Output file bisa anda letakkan di manapun, tidak harus
di /var/cscope/kernel, karena ini hanya contoh. Nama file pun tidak
harus cscope.files, tapi untuk memudahkan percobaan kita gunakan nama
ini karena nama ini otomatis dikenali sebagai daftar file yang akan
di-index oleh Cscope. Opsi "-prune" berarti kita tidak
melakukan recursive lookup ke dalam subdirektori. Ini digunakan
karena file-file yang diperlukan ada di direktori level pertama.
Extension yang diperlukan adalah listing C (*.c dan *.h) serta
beberapa statement assembly ( *.s, *.S, dan *.x).
Simpan
file script di atas, misal dengan nama "generate.sh" di
direktori
/var/cscope/kernel.
#
mkdir -p /var/cscope/kernel
#
chmod a+x ./generate.sh
Lalu
jalankan script "generate.sh". Tunggu beberapa saat
(tergantung kecepatan komputer anda) dan terciptalah file
"cscope.files" di direktori /var/cscope/kernel. Isi dari
file ini kurang lebih sebagai berikut.
[root@mulyadi1
kernel]# head -20 ./cscope.files
/usr/src/linux-2.4/Documentation/DocBook/procfs_example.c
/usr/src/linux-2.4/Documentation/networking/ifenslave.c
/usr/src/linux-2.4/abi/cxenix/pathconf.c
/usr/src/linux-2.4/abi/cxenix/misc.c
/usr/src/linux-2.4/abi/cxenix/signal.c
/usr/src/linux-2.4/abi/cxenix/stubs.c
/usr/src/linux-2.4/abi/cxenix/sysent.c
/usr/src/linux-2.4/abi/cxenix/utsname.c
/usr/src/linux-2.4/abi/ibcs/sysent.c
/usr/src/linux-2.4/abi/isc/sysent.c
/usr/src/linux-2.4/abi/isc/misc.c
/usr/src/linux-2.4/abi/sco/ptrace.c
/usr/src/linux-2.4/abi/sco/ioctl.c
/usr/src/linux-2.4/abi/sco/misc.c
/usr/src/linux-2.4/abi/sco/mmap.c
/usr/src/linux-2.4/abi/sco/secureware.c
/usr/src/linux-2.4/abi/sco/stat.c
/usr/src/linux-2.4/abi/sco/statvfs.c
/usr/src/linux-2.4/abi/sco/sysent.c
/usr/src/linux-2.4/abi/sco/tapeio.c
Setelah
anda cek entry file cscope.files, berikutnya lakukan indexing (tetap
di direktori /var/cscope/kernel):
#
cscope -b -k
Tunggu
beberapa saat dan akan menghasilkan file cscope.out. File inilah yang
merupakan database untuk pencarian fungsi atau variabel oleh Cscope.
Sekarang
kita test dulu fungsi dasar Cscope dengan menggunakan interface
pencarian built in, dengan mengetik perintah berikut.
#
cscope -d
Opsi
-d mencegah Cscope melakukan indexing ulang (karena anda sudah
membuat index sebelumnya dan tidak melakukan perubahan apapun di
source code). Anda akan menemui tampilan seperti gambar 1.

Gambar
1. Tampilan awal interface cscope
Sebagai
latihan, kita mulai dengan mencari definisi fungsi schedule. Misal
sekarang kursor berada pada baris "Find this C symbol".
Tekan [Enter] sekali, maka kursor akan berpindah ke "Find this
global definition". Sesuai namanya, baris "Find this global
definition" akan mencari seakurat mungkin suatu definisi, bukan
sekadar deklarasi suatu fungsi atau struct atau variabel. Misal dalam
hal ini kita cari deklarasi task struct, maka isikan task_struct lalu
[Enter], anda akan mendapat tampilan seperti gambar 2.

Gambar
2. hasil pencarian task_struct
Pada
bagian atas, anda akan menemukan hasil pencarian. Pada komputer
penulis ditemukan 6. Di sini terlihat jelas, yang kemungkinan adalah
deklarasi task_struct adalah di nomor 5, maka tinggal tekan keyboard
angka 5. Seketika Cscope akan memanggil Vi (editor default sesuai
variabel lingkungan EDITOR) dan display akan "meloncat" ke
deklarasi task_struct. Tekan [Esc] [:] [q] [Enter] untuk keluar dari
Vi.

Gambar
3. Cscope memanggil Vi pada posisi deklarasi task_struct
Bagaimana?
Mulai merasakan kehandalan Cscope? Kita coba tipe pencarian lain.
Misal kita ingin mencari prosedur apa saja untuk memanggil fungsi
schedule_task (ini suatu fungsi untuk memasukkan tugas ke dalam
antrian scheduler kernel). Jika anda sebelumnya berada pada baris
hasil pencarian task_struct, tekan [Tab]. Tombol [Tab] digunakan
untuk berpindah antara baris hasil pencarian dan baris fungsi
pencarian. Lalu tekan kursor panah atas atau bawah dan arahkan ke
"Find functions calling this function". Ketik schedule_task
dan [Enter]. Kali ini anda akan mendapat cukup banyak hasil.

Gambar
4. hasil pencarian fungsi yang memanggil schedule_task
Pada
komputer penulis mendapatkan 64 referensi. Gunakan tombol '+' dan '-'
untuk bergerak maju dan mundur melihat seluruh hasil pencarian. Anda
akan menemui kata-kata "Press space bar to display the first
line again" jika anda sudah mencapai akhir dari pencarian. Tekan
Space bar untuk kembali ke awal. Ambil salah satu hasil dan seperti
tadi tekan angka yang sesuai. Misal penulis pilih pada referensi file
sys., maka tampilan akan berpindah ke Vi dan meloncat ke pemanggilan
schedule_task.

Gambar
5. Pemanggilan schedule_task di file sys.c
Setelah
selesai mencek dengan editor (Vi), keluar dengan [Esc] [:] [q]
[Enter].
Barangkali
anda berpikir, bolak balik dari interface cscope ke Vi, sepertinya
tidak efisien. Benar! Karena itu sekarang saatnya kita coba
integrasikan Vi/Vim dengan Cscope. Di sini diasumsikan anda
menggunakan Vim versi 6.1-2 bawaan Red Hat 7.3. Jika anda belum
menginstall Vim, lakukan instalasi paket-paket RPM berikut:
#
rpm -Uvh /lokasi/RPM/distro/anda/vim-common-6.1-2.i386.rpm
#
rpm -Uvh /lokasi/RPM/distro/anda/vim-enhanced-6.1-2.i386.rpm
#
rpm -Uvh /lokasi/RPM/distro/anda/vim-minimal-6.1-2.i386.rpm
Pada
distro Red Hat, lokasi RPM bisa anda temukan di direktori
RedHat/RPMS.
Kemudian
download file http://cscope.sourceforge.net/cscope_maps.vim.
http://cscope.sourceforge.net/cscope_maps.vim.
1. Copykan
ke home direktori user yang akan menggunakan vim untuk penjelajahan.
2. Cek
apakah anda sudah memiliki file .vimrc
#
ls -al ~/ | grep -i vimrc
3. Jika
ternyata sudah ada, sisipkan baris berikut di .vimrc:
source
cscope_maps.vim
4. Jika
belum ada file vimrc, untuk praktisnya rename saja file map menjadi
vimrc, karena pada intinya file map vim juga berisi syntax yang sama
dengan perintah-perintah vimrc:
#
mv ~/cscope_maps.vim ~./.vimrc
Awas, jangan sampai
tanda "." ketinggalan ]!
Pastikan
file .vimrc dan/atau file map bisa dibaca dan dieksekusi.
#
chmod 755 ~/.vimrc
#
chmod 755 ~/cscope_maps.vim
Berikutnya,
perhatikan sejenak isi dari cscope_maps ini (berikut cuplikannya):
(baris
40) “ add any cscope database in current directory
(baris
41) if filereadable("cscope.out")
(baris
42) cs add cscope.out
(baris
43) “ else add the database pointed to by environment variable
(baris
44) elseif $CSCOPE_DB != ""
(baris
45) cs add $CSCOPE_DB
(baris
46) endif
Di
baris 44, ada variabel lingkungan CSCOPE_DB yang bisa anda set untuk
menunjukkan lokasi data hasil indexing Cscope seperti yang telah kita
lakukan di atas (dengan perintah cscope -b -k). Cara paling mudah
menambahkannya adalah menyisipkan baris berikut di .bashrc (pada home
direktori user yang ada edit file .vimrc nya):
export
CSCOPE_DB=/var/cscope/kernel/cscope.out
Kelak
jika membutuhkan lebih dari satu database, tambahkan lewat cs add
setelah baris endif (baris 46) di .vimrc, misalnya:
cs
add /var/cscope/kernel2/cscope.out
Sekarang
percobaan dengan Vim bisa dimulai. Log out dan log in sebagai user
(yang tadi telah anda modifikasi .vimrc-nya) lalu panggil vim sambil
mengedit suatu file, misal:
#
vim /usr/src/linux-2.4/kernel/sched.c
Sebelumnya
mungkin perlu penulis beritahukan, dalam artikel ini tidak dijelaskan
secara detil bagaimana mengoperasikan Vi. Jadi pembaca bisa memadukan
dengan membaca fasilitas help di Vi. Mungkin di lain waktu akan
dicoba menulis artikel mengenai Vi. Kembali ke Vim, coba cari kata
"interruptible_sleep_on_timeout", kursor akan berpindah ke
deklarasi seperti berikut (tekan 'n' beberapa kali sampai
menemukannya) :
long
interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout)
{
SLEEP_ON_VAR
current->state =
TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD
timeout =
schedule_timeout(timeout);
SLEEP_ON_TAIL
return timeout;
}
Sekarang
coba arahkan kursor ke schedule_timeout di dalam deklarasi
interruptible_sleep ini. Lalu tekan [Ctrl] [SpaceBar], lalu lepas,
secepatnya dan tekan [g].

Gambar
6. Perintah lewat keyboard untuk pencarian fungsi di Vim
Perhatikan,
sejenak di layar terbawah Vim (baris perintah) ada tertera:
scs
find g schedule timeout
Ini
adalah perintah yang sebenarnya dikirimkan ke Vim, namun karena kita
telah memasang file mapping, kita tidak perlu mengetikkan perintah
yang panjang ini.

Gambar
7. Hasil pencarian schedule_timeout di Vim
Mirip
dengan hasil pencarian di dalam interface cscope, anda akan disuguhi
window hasil pencarian. Tekan angka '4' dan Vim akan men-split
tampilan menjadi dua window, satu masih menunjuk di
interruptible_sleep, window satunya menunjuk ke schedule_timeout.

Gambar
8. Vim menampilkan dua window untuk memudahkan crosscheck.
Nah,
menyenangkan bukan? Anda bisa mempelajari isi fungsi
interruptible_sleep_on_timeout sekaligus melihat isi
schedule_timeout. Untuk berpindah antar window, tekan tombol [Ctrl]
[W] diikuti [w] (hampir bersamaan). Untuk menutup salah satu window,
tekan [Esc] [:] [q] [Enter].
Kita
lanjutkan dengan mengonfigurasi Ctags untuk indexing kernel. Mungkin
anda bertanya, "Buat apa Ctags, sepertinya fungsinya sama saja
Cscope?" Jawaban ini benar, tapi alasan utama kita menggabungkan
dua sistem ini (Cscope dan Ctags) adalah untuk memaksimalkan kinerja
pencarian. Karena masing-masing tools ada kelebihan dan kekurangan
dalam membuat database, maka cara terbaik adalah menggunakan dua
database hasil Cscope dan Ctags.
Sekarang
kita langsung membuat database Ctags. Mirip seperti Cscope, kita
gunakan file generate.sh untuk menghasilkan daftar file yang akan
di-index. Langkahnya sebagai berikut:
1. Buat
direktori untuk menampung database ctags, misal
# mkdir -p
/var/ctags/kernel
2. Copykan
file generate.sh ke direktori diatas dan ubah baris berikut :
-name
"*.[chxsS]" -print > /var/cscope/kernel/cscope.files
menjadi
-name
"*.[chxsS]" -print > /var/ctags/kernel/kernel.file
Intinya nama output apapun bisa dipakai
asalkan memudahkan identifikasi untuk input Ctags. Jangan lupa chmod
a+x ./generate.sh.
3. Jalankan
generate.sh dan lakukan indexing.
# cd /var/ctags/kernel
# ./generate.sh
# ctags -L ./kernel.file
Akan tercipta file "tags" di
direktori /var/ctags/kernel.
4. Berikutnya
tambahkan baris berikut di file .vimrc atau cscope_maps.vim.
set
tags=/var/ctags/kernel/tags
Idealnya letakkan perintah di atas setelah
"set cscopeverbose". Ini untuk memasukkan database Ctags
tiap kali Vim anda jalankan. Anda bisa memasukkan sekaligus beberapa
file tag dengan dipisahkan koma (",")
5. Edit
"set csto=0" pada .vimrc dan cscope_map.vim menjadi set
csto=1. Ini untuk menset agar pencarian tag mendahulukan database
Ctags, baru jika tidak ditemukan, digunakan database Cscope. Kalau
dibalik, anda akan mendahulukan Cscope baru Ctags. Ini sebenarnya
masalah selera saja, tapi untuk memaksimalkan fungsi keduanya, kita
coba dulu setting di atas.
Kombinasi
Vim, Cscope, dan Ctags sudah lengkap. Saatnya mencoba kemampuan
ctags. Coba jalankan Vim berikut ini.
#
vim /usr/src/linux-2.4/kernel/timer.c
Sekarang
coba cari kata "spin_lock_irqsave". Anda bisa temukan di
sekitar baris ke 180-185. OK, sudah ketemu? Kalau masih kesulitan,
lakukan langkah berikut (fungsi search): Tekan [Esc] [Esc] [/]
spin_lock_irqsave, artinya tekan [Esc] dua kali, ketik [/], lalu
ketikkan spin_lock_irqsave.
Setelah
menemukan yang anda cari, arahkan kursor agar pas berada di dalam
kata "spin_lock_irqsave", lalu tekan [Ctrl] dan []]
(tombol Ctrl dan karakter ']').

Gambar
9. hasil pencarian Ctags pada keyword spin_lock_irqsave
Pilih
salah satu, misalnya di nomor 5. Begitu anda tekan [Enter], anda akan
dibawa ke definisi spin_lock_irqsave. Jika kebetulan anda menemukan
definisi lain yang ingin anda telusuri, arahkan kursor ke definisi
tersebut lalu tekan [Ctrl] []]. Anda bisa mundur ke langkah
sebelunnya dengan [Ctrl] [T]. Pencarian berlapis ini disebut "tag
stack" dan bisa dibayangkan seperti tumpukan atau stack.
Jadi
secara umum (untuk bernavigasi di stack):
1. [Ctrl]
[]] untuk mencari definisi tag. Jika ditemukan hanya satu hasil, Vim
akan langsung "meloncat" ke definisi tersebut. Jika ada
lebih dari satu, maka hasil pencarian akan ditampilkan dan anda bisa
memilih angka hasil pencarian
2. [Ctrl]
[T] untuk mundur ke posisi teks sebelumnya. Jika anda sudah berada di
posisi paling awal, anda akan menemui teks "at bottom of tag
stack". Jika anda belum melakukan pencarian tag, [Ctrl] [T] akan
memunculkan peringatan "tag stack empty".
Barangkali
anda ingin melakukan split window seperti saat kita mencoba Cscope.
Anda punya dua pilihan:
1. Menggunakan
"stjump" dan keyword yang akan dicari. Ketikkan
[Esc] [:] stjump
<keyword-yang-anda-cari>
2. keluar
dulu dari Vi, lalu tambahkan baris berikut di .vimrc.
nmap <C-@>k
:stjump <C-R>=expand("<cword>")<CR><CR>
Perintah ini artinya membuat
mapping/shortcut tombol [Ctrl] [Spacebar] [k] untuk melakukan "stjump
" seperti point 1.
Sekarang
lakukan lagi seperti pencarian tag, hanya saja kali ini lakukan
dengan "stjump" atau shortcut kita yang baru. Setelah kita
meloncat ke lokasi teks yang baru, otomatis window akan di-split
menjadi dua. Kita bisa sekaligus melihat ke dua bagian (mirip dengan
Cscope). Jika anda tidak suka dengan split secara horisontal, Cscope
dan Ctags bisa melakukan split window vertikal dengan bantuan Vim,
caranya sebagai berikut.
1. Untuk
Cscope, tekan [Ctrl] [Spacebar] [Spacebar] (artinya dua kali space
bar secara cepat) diikuti [g].
2. Untuk
Ctags, keluar dulu dari Vim dan tambahkan mapping berikut di .vimrc.
nmap <C-@><C-@>k
:vert stjump <C-R>=expand("<cword>")<CR><CR>
Jalankan
lagi Vim, letakkan kursor di keyword yang dicari definisnya lalu
tekan [Ctrl] [Spacebar] [Spacebar] [k]. Setelah memilih tag tujuan
(atau langsung meloncat jika hanya ditemukan satu hasil pencarian),
window akan displit secara vertikal.
Kedua
cara di atas bisa diterapkan secara rekursif dan juga berlaku untuk
split horisontal. Anda bisa juga mengkombinasikan split horisontal
dan vertikal.

Gambar
10. Contoh tampilan dengan split window di Vim
Untuk
berpindah antar window yang "ruwet" seperti ini, gunakan
[Ctrl] [W] diikuti panah kursor atas, bawah, kiri dan kanan. Fokus
window akan berpindah sesuai arah kursor yang ditekan. Jika sudah
selesai dengan satu window anda bisa tutup dengan perintah menekan
[Esc] [:] [q].
Kesimpulannya,
dengan Vim, Ctags dan Cscope anda bisa menjadi lebih cepat dan
produktif dalam menelusuri suatu kode program yang sangat besar dan
terpisah-pisah. Contoh dalam artikel ini bisa anda terapkan tidak
hanya untuk development kernel, tapi juga projek lainnya. Selamat
mencoba, variasi lain bisa anda perdalam sendiri dengan bekal
tutorial ini dan mengamati contoh file mapping cscope.
Mulyadi S.
(a_mulyadi@telkom.net)