vim + screen + gdbでデバッグしよう
FreeBSD上でvimをデバッグする環境を整えよう。
以下、カレントディレクトリはvimのsrcディレクトリであるとします。
gdbでアタッチ
まずは素朴に
$ gdb vim
としてみる。vimが立ち上がるが、端末がvimに占居されて
しまい、gdbを操作できなくなるので当然ダメ。そこで
$ gdb --args vim -g
とgvimを起動してみる。するとgvimウィンドウが現れるのだが…
(gdb) run Starting program: /usr/home/ao/dl/vim70/src2/vim -g Program exited normally. (gdb)
なんとプロセスがすでに正常終了している。
まだウィンドウが目の前に出ているのに。
GUIモードを起動するときfork()しているからだろうか。
ではあらかじめgvimを起動しておき、gdbでアタッチするしかないのかな。
[追記:2006-08-29]
vim -g -f
で fork せずに gvim を起動できることが判明。
$ vim -g $ gdb -p 61373
ここで、私の環境では次のようなエラーが出てしまった。
Attaching to process 61373 /usr/src/gnu/usr.bin/gdb/libgdb/../../../../contrib/gdb/gdb/solib-svr4.c:1443: internal-error: legacy_fetch_link_map_offsets called without legacy link_map support enabled. A problem internal to GDB has been detected, further debugging may prove unreliable. Quit this debugging session? (y or n)
詳しい原因はよくわからないが、ググってみたところ
$ gdb -p 61373 /xxx/vim
というように、プロセスIDに加えて実行ファイルの絶対パスをつけて起動すれば
アタッチできるらしい。
0x2886c697 in poll () from /lib/libc.so.5 (gdb)
できた。
しかし毎回プロセスIDを指定するのは面倒なので、ちょっとスクリプトを書いてみた。
#!/usr/bin/env ruby vimpath = Dir.pwd + "/vim" vimarg = " -g " + ARGV.join(" ") vimcmd = vimpath + vimarg pid = fork() if pid #puts pid r = Process.wait if r == pid sleep 0.3 # まだ親vimと子vim両方生きてるので少し待つ p = IO.popen("ps x") cnt = 0 p.gets # 見出しの行を捨てる backref = {} lines = p.readlines() lines.each_with_index do |x, i| if x.split[4] == vimpath puts "#{cnt+1}: " + x cnt += 1 backref[cnt] = i end end if cnt > 1 puts "Some processes found." print "Which one? : " a = STDIN.gets.to_i - 1 if not backref[a] puts "Wrong number." exit end lineidx = backref[a] elsif cnt == 1 lineidx = backref[1] else puts "No vim process found." exit end debuggeepid = (lines[lineidx].split)[0] cmd = "gdb -p #{debuggeepid} #{vimpath}" puts cmd exec(cmd) end else exec(vimcmd + ";") end
思いのほか複雑になってしまった。
gvimを起動し、gdbでアタッチするところまでを自動的にやってくれる。
GUIでないvimをデバッグしたい場合はgvimの代わりにkterm -e /xxx/vim
とすればいいだろう。
これで今、gvimはサスペンドし、gdbに制御が移っている。
c(continue)すれば gvim が再開する。
(gdb) c
screenを使ってgdbを操作する
vimでソースを見ながらgdbでデバッグしたい。
そこでscreenを使って画面を2分割し、vimからscreenコマンドを発行して
gdbを操作する。力技。
" カレントファイルのカレント行にブレークポイント設定 command! Breakpoint call system("screen -X eval focus 'stuff \"b " . expand("%") . ":" . line(".") . "\"\\015' focus") " ステップ実行 command! Step call system("screen -X eval focus 'stuff s\\015' focus") " 実行再開 command! Continue call system("screen -X eval focus 'stuff c\\015' focus") " ステップ実行(関数内に入らない)。なんと:Nextはすでに予約されている! command! NextStep call system("screen -X eval focus 'stuff c\\015' focus") " カレントファイルのカレント行まで実行 command! Advance call system("screen -X eval focus 'stuff \"advance " . expand("%") . ":" . line(".") . "\"\\015' focus") " 変数の値を表示。なんと:Printはすでに予約されている! command! -nargs=+ PrintVariable call system("screen -X eval focus 'stuff \"p " . "<args>" . "\"\\015' focus")
とりあえずこれくらいできれば使いものになるだろうか?
頑張ればもっと色々できるかもしれない。
[追記:2006-12-9]
上の Ruby スクリプトをやめて、こんなシェルスクリプトを作った。
gdbvim
#!/bin/bash if [ $# -eq 0 ]; then ./vim -f -g out & vimpid=$! else ./vim -f -g "$@" & vimpid=$! fi echo $vimpid >| ./vimpid exec gdb -p $vimpid `pwd`/vim # gdb 内で continue をするとデバッグ開始 # stopvim で vim をサスペンド
stopvim
#!/bin/sh if [ -f vimpid ]; then kill -STOP `cat vimpid` else echo "No file vimpid" fi
さらに screen を操作するためにこんなシェル関数を作った。
# screen のリージョンを分割してそちらにフォーカスを移し、新しいシェルを開いて、今のリージョンと同じディレクトリに cd sp() { screen -X eval split focus screen 'stuff cd\040'"$PWD"'\015' } cl() { screen -X remove # close current window } bd() { screen -X remove # close current window exit }