VimのソースコードをVimで解析しよう(Part1-4)

(Part1-1)(Part1-2)(Part1-3)


さぁ、デバッガで動かして関連性を見てみましょう。デバッガは昔ながらのGDBを使います。GDBのフロントエンドはいくつか存在しますが、「昔ながら」でいきましょう。
man gdb
GDBマニュアル
ファイヤープロジェクト GDB ←わかりやすい!!

デバッグ用バイナリの作成

GDBでCソースコードデバッグするにはデバッグ情報入りのバイナリを生成する必要があります。
bash

$ cd vimソースをゲットしたディレクトリ
# 再生成可能なファイルを削除
$ make distclean
# デバッグ情報あり(-g)でconfigure (configureオプションはお好みで)
$ ./configure CFLAGS="-g" --enable-multibyte --with-gnome --with-features=big
$ make

デバッグ準備

実行中のvimプロセスのデバッグをするので端末(ターミナルエミュレータ)が2つ必要です。*1

  • 端末V … Vim実行用 (ウィンドウタイトルは Vim)
  • 端末G … gdb実行用 (ウィンドウタイトルは GDB)

端末Vで先ほど生成したVimを起動しましょう。

$ cd vimソースをゲットしたディレクトリ/src
$ ./vim

j でちゃんとカーソル下移動になるように下準備しましょう。

" 相対行番号表示on
:set relativenumber[Enter]
" インサートモードへ移行→ a改行b を入力→ノーマルモードへ移行
ia<CR>b<Esc>
" カーソルを1行目へ移動
k


端末Vは準備OK。
次は端末Gです。

# 現在実行中のvimプロセスのIDを表示
$ pgrep vim
22063
# GDBでプロセスID 22063の ./vim に接続
gdb ./vim 22063


「分離されたデバッグ情報がないよ」とか言われてますが、ライブラリのデバッグをする訳じゃないのでスルーして、これまで調べた3つの処理にbreakをセットしましょう。

(gdb) b screen.c:3482
Breakpoint 1 at 0x53a26b: file screen.c, line 3482.
(gdb) b screen.c:8644
Breakpoint 2 at 0x543b6e: file screen.c, line 8644.
(gdb) b edit.c:7116
Breakpoint 3 at 0x42caa2: file edit.c, line 7116.
(gdb)
breakpoint # 関数名 ファイル名 行番号 処理内容
1 win_line() screen.c 3482 相対行番号の描画データ設定処理
2 setcursor() screen.c 8644 カーソル描画処理
3 cursor_down() edit.c 7116 カーソル移動処理

デバッグ開始

Vimの実行を継続させます。

(gdb) c
Continuing.

端末VのVimに j を入力すると、端末GのGDBがbreakしました。変数の値を表示したりした後、バックトレース表示させ継続(c)を繰り返すと以下のような流れになりました。

Breakpoint 2, setcursor () at screen.c:8644
8644            windgoto(W_WINROW(curwin) + curwin->w_wrow,
(gdb) p curwin->w_wrow
$2 = 0
(gdb) bt
#0  setcursor () at screen.c:8644
#1  0x00000000004f1377 in display_showcmd () at normal.c:4038
#2  0x00000000004f1210 in add_to_showcmd (c=106) at normal.c:3960
#3  0x00000000004eb78a in normal_cmd (oap=0x7fff51cd2a90, toplevel=1) at normal.c:707
#4  0x00000000005c6169 in main_loop (cmdwin=0, noexmode=0) at main.c:1263
#5  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.

Breakpoint 3, cursor_down (n=1, upd_topline=1) at edit.c:7116
7116            curwin->w_cursor.lnum = lnum;
(gdb) p lnum
$4 = 2
(gdb) bt
#0  cursor_down (n=1, upd_topline=1) at edit.c:7116
#1  0x00000000004f5231 in nv_down (cap=0x7fff51cd29d0) at normal.c:6191
#2  0x00000000004ec65f in normal_cmd (oap=0x7fff51cd2a90, toplevel=1) at normal.c:1193
#3  0x00000000005c6169 in main_loop (cmdwin=0, noexmode=0) at main.c:1263
#4  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.

Breakpoint 2, setcursor () at screen.c:8644
8644            windgoto(W_WINROW(curwin) + curwin->w_wrow,
(gdb) p curwin->w_wrow
$5 = 1
(gdb) bt
#0  setcursor () at screen.c:8644
#1  0x00000000004f1377 in display_showcmd () at normal.c:4038
#2  0x00000000004f10e8 in clear_showcmd () at normal.c:3897
#3  0x00000000004ec99c in normal_cmd (oap=0x7fff51cd2a90, toplevel=1) at normal.c:1333
#4  0x00000000005c6169 in main_loop (cmdwin=0, noexmode=0) at main.c:1263
#5  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.

Breakpoint 1, win_line (wp=0x20a97d0, lnum=1, startrow=0, endrow=21, nochange=1) at screen.c:3482
3482                            sprintf((char *)extra, "%*ld ",
(gdb) bt
#0  win_line (wp=0x20a97d0, lnum=1, startrow=0, endrow=21, nochange=1) at screen.c:3482
#1  0x0000000000536c14 in win_update (wp=0x20a97d0) at screen.c:1850
#2  0x00000000005342fb in update_screen (type=35) at screen.c:531
#3  0x00000000005c5f28 in main_loop (cmdwin=0, noexmode=0) at main.c:1166
#4  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.

Breakpoint 1, win_line (wp=0x20a97d0, lnum=2, startrow=1, endrow=21, nochange=1) at screen.c:3482
3482                            sprintf((char *)extra, "%*ld ",
(gdb) bt
#0  win_line (wp=0x20a97d0, lnum=2, startrow=1, endrow=21, nochange=1)
    at screen.c:3482
#1  0x0000000000536c14 in win_update (wp=0x20a97d0) at screen.c:1850
#2  0x00000000005342fb in update_screen (type=35) at screen.c:531
#3  0x00000000005c5f28 in main_loop (cmdwin=0, noexmode=0) at main.c:1166
#4  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.

Breakpoint 2, setcursor () at screen.c:8644
8644            windgoto(W_WINROW(curwin) + curwin->w_wrow,
(gdb) p curwin->w_wrow
$9 = 1
(gdb) p curwin->w_wcol
$10 = 4
(gdb) bt
#0  setcursor () at screen.c:8644
#1  0x00000000005c6087 in main_loop (cmdwin=0, noexmode=0) at main.c:1214
#2  0x00000000005c5b44 in main (argc=1, argv=0x7fff51cd2db8) at main.c:964
(gdb) c
Continuing.


デバッグ終了

端末Gのgdbはctrl-Cして止めた後、qで終了します。

^C
Program received signal SIGINT, Interrupt.
0x0000003a35ad9073 in __select_nocancel () from /lib64/libc.so.6
(gdb) q
A debugging session is active.

        Inferior 1 [process 26051] will be detached.

Quit anyway? (y or n) y
Detaching from program: /home/h_east/samba/Mercurial/vim_new/src/vim, process 26051

端末VのVimGDBから切り離されたので通常通り :q! で終了できます。

考察

No. bp# 解説
1 2 私の環境ではset showcmdしてるのでその描画処理内で呼ばれている
(今回の件とはたぶん関係ないのでスルー)
2 3 カーソル下移動
3 2 showcmdの消去処理内で呼ばれている
(今回の件とはたぶん関係ないのでスルー)
4 1 ウィンドウの1行目の描画
5 1 ウィンドウの2行目の描画
6 2 2行目5カラム目にカーソル描画(相対行番号表示エリアも込みの位置)

今回の表示位置がずれる件に関係ありそうなのは No.2, 5, 6 ですね。
現象が起こせないのがもどかしいですが、ずれが発生する要因としては以下のようなパターンが考えられます。

  • No.2, No.5は処理されたが No.6が処理されなかった。

ソースコード的には setcursor()内すぐの redrawing() の判定がFALSEの場合に上記パターンになります。
redrawing()を見てみましょう。

/*
 * Return TRUE if redrawing should currently be done.
 */
    int
redrawing()
{
    return (!RedrawingDisabled
                       && !(p_lz && char_avail() && !KeyTyped && !do_redraw));
}

コメントには「再描画が必要な場合は TRUE を返す」って書いてます。
NOT(!)とAND(&&)で分かりずらい。。えーと、TRUEになる条件は

  • RedrawingDisabled が FALSE である
  • かつ (
    • 'lazyredraw'オプション がoff または
    • char_avail() が FALSE または
    • KeyTyped が TRUE または
    • do_redraw が TRUE )

'lazyredraw'オプションはよっぽどのことがない限り off のはずなので(要確認)、結局 RedrawingDisabled の状態に依存していると思われます。
再現手順を確立させてGDBwatchコマンドで追いかければ原因がわかるかも!?

終わり

原因解明出来ていないですがPart1を終わります。
選んだ題材がいきなりハードル高すぎましたw
ですが、Vimを使用してのコードリーディングの一連の手順、GDBでのデバッグ方法は書きましたので参考になれば幸いです。
リクエストや質問や指摘ありましたら twitterやコメントでお気軽にどうぞ。

Part2はゴールデンウィークか!?

*1:Vimの起動直後からキー入力待ちまでの部分のデバッグをおこなう場合は1つじゃないと無理です。