Internet of Tomohiro
よろしければ投げ銭をお願いします。
GDB はコマンドライン上でプログラムをデバッグするツールです。 GDBを使えばプログラムを実行中に特定の箇所で停止して変数の値を見たり一行づつプログラムを実行することなどができます。 GDBについて詳しく知りたい方は GDB User Manual を参照して下さい。
GDBがインストールされているかどうかは以下のコマンドで確認できます。
On Linux:
which gdb
On Windows:
where gdb
もしインストールされていなければパッケージマネージャ(apt, pacman, emergeなど)を使ってインストールしてください。 GDBはMinGWに含まれています。 Windowsで Scoop というパッケージマネージャを使う場合、 scoop install gcc でgccをインストールするとGDBも一緒にインストールされます。 scoop install gdb でGDBだけをインストールできますが、gccに付属するGDBよりも古いです。 Scoop を使いたくなければ TDM-GCC からダウンロードできます。
GDBでデバッグするには以下のように --debugger:native オプションをつけてコンパイルします。
nim c --debugger:native test.nim
以下のコードを test.nim に保存しGDBでデバッグしてみます。
type TestObj = object num: int val: float str: string proc initTestObj(num: int): TestObj = TestObj(num: num, val: 3.141, str: "TestObj") proc foo(x: int): int = let y = x + 2 return y * 10 proc bar(x: int): int = if x == 3: return foo(x) return x * 100 proc main = var a = 1 a += 3 let str = "foobar" var seq1 = @[0, 1, 2, 3, 4] a = bar(1) a = bar(2) a = bar(3) let tobj = initTestObj(11) main()
このコードを --debugger:native をつけてコンパイルした後に nim-gdb に読み込ませます。
nim c --debugger:native test.nim nim-gdb test
もしnim-gdbが無い場合は Nim repository から bin/nim-gdb (Windowsの場合 bin/nim-gdb.bat)と tools/debug/nim-gdb.py をダウンロードして下さい。 nim-gdbを使わない場合はGDBを起動した後に source tools/debug/nim-gdb.py を実行して下さい。 nim-gdbはGDBを実行し tools/debug/nim-gdb.py を読み込ませるbashスクリプトです。 nim-gdb.pyはNimの変数がGDBで綺麗に表示されるようにするためのPythonスクリプトです。
nim-gdbはbashスクリプトなのでWindowsではbash等をインストールしないとそのまま使えません。 なので以下のようにして tools\debug\nim-gdb.py をGDBに読み込む必要があります。 GDB起動後に
source {path-to-nim}\tools\debug\nim-gdb.py
を実行するか以下のようにGDBを起動して下さい。
gdb -eval-command "source {path-to-nim}\tools\debug\nim-gdb.py" test
nim-gdbが実行されると以下のようなメッセージが表示されます。 実行ファイルにデバッグに必要な情報が含まれていれば Reading symbols from test...done. と表示されるはずです。 もし --debugger:native オプションをつけ忘れてコンパイルすると Reading symbols from test...(no debugging symbols found)...done と表示されます。
GNU gdb (Gentoo 8.1 p1) 8.1 Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://bugs.gentoo.org/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from test...done. (gdb)
quit または q コマンドを実行するか Ctrl-d キーを押すとGDBを終了できます。
まずbreak pointを設定してみます。コードの中にブレークポイントを設定することによって、プログラムが実行中にその場所に到達すると一時停止させることができます。 b または break コマンドにプロシージャ名を与えることによってそのプロシージャが始まる場所にbreak pointを設定できます。 GDBではNimで書いたプロシージャの名前を直接指定することができず、プロシージャ名にハッシュ値が付いた名前で指定する必要があります。 例えば、 foo という名前のプロシージャにbreak pointを設定するときは break foo_ と入力した後にtabキーを入力するとプロシージャ名についたハッシュ値が補完されます。
(gdb) break foo_yBfYXi2FBVfUOybMAWEXjA_2 Breakpoint 1 at 0x10544: file /tmp/tmp/test.nim, line 10.
ファイル名と行番号を指定してbreak pointを設定することもできます。
(gdb) break test.nim:21 Breakpoint 2 at 0x1088a: file /tmp/tmp/test.nim, line 21.
run コマンドでプログラムを実行します。 先程設定したtest.nimの21行目のbreak pointでプログラムが停止します。 なので a += 3 が実行される直前で停止します。
Starting program: /tmp/tmp/test Breakpoint 2, main_Ak9bvQf5hr5bnkcYq9cDinOw () at /tmp/tmp/test.nim:21 21 a += 3
print または p コマンドで変数の中身を見ることができます。
(gdb) print a $1 = 1
next または n コマンドで一行だけ実行します。 このときに a += 3 が実行されます。
(gdb) next 22 let str = "foobar" (gdb) print a $2 = 4
next コマンドに実行したい行数を指定することができます。
(gdb) next 3 25 a = bar(2) (gdb) print a $3 = 100
step または s コマンドで次の行で呼ばれるプロシージャの中に入ることができます。
(gdb) step bar_yBfYXi2FBVfUOybMAWEXjA (x=2) at /tmp/tmp/test.nim:14 14 proc bar(x: int): int =
finish または fin コマンドで今いるプロシージャから抜けるまでプログラムを実行します。
(gdb) finish Run till exit from #0 bar_yBfYXi2FBVfUOybMAWEXjA (x=2) at /tmp/tmp/test.nim:14 0x000055555556494b in main_Ak9bvQf5hr5bnkcYq9cDinOw () at /tmp/tmp/test.nim:25 25 a = bar(2) Value returned is $4 = 200 (gdb) next 26 a = bar(3) (gdb) print a $5 = 200
continue または c コマンドでプログラムが終了するかbreakpointに引っかかるまで実行されます。
(gdb) continue Continuing. Breakpoint 1, foo_yBfYXi2FBVfUOybMAWEXjA_2 (x=3) at /tmp/tmp/test.nim:10 10 proc foo(x: int): int =
backtrace または bt コマンドでbacktraceを表示します。 main プロシージャの26行目から bar(3) プロシージャが呼ばれ、 bar プロシージャの16行目から foo(3) プロシージャが呼ばれているのがわかります。
(gdb) backtrace #0 foo_yBfYXi2FBVfUOybMAWEXjA_2 (x=3) at /tmp/tmp/test.nim:10 #1 0x0000555555564697 in bar_yBfYXi2FBVfUOybMAWEXjA (x=3) at /tmp/tmp/test.nim:16 #2 0x000055555556496c in main_Ak9bvQf5hr5bnkcYq9cDinOw () at /tmp/tmp/test.nim:26 #3 0x0000555555564b38 in NimMainModule () at /tmp/tmp/test.nim:29 #4 0x0000555555564a46 in NimMainInner () at /tmp/tmp/Nim/lib/system.nim:3154 #5 0x0000555555564a82 in NimMain () at /tmp/tmp/Nim/lib/system.nim:3162 #6 0x0000555555564ad0 in main (argc=1, args=0x7fffffffde58, env=0x7fffffffde68) at /tmp/tmp/Nim/lib/system.nim:3169
list または l コマンドで現在いる場所付近のソースコードを表示します。
(gdb) list 5 str: string 6 7 proc initTestObj(num: int): TestObj = 8 TestObj(num: num, val: 3.141, str: "TestObj") 9 10 proc foo(x: int): int = 11 let y = x + 2 12 return y * 10 13 14 proc bar(x: int): int =
info breakpoints または info break コマンドでbreak pointのリストを表示します。
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x0000555555564544 in foo_yBfYXi2FBVfUOybMAWEXjA_2 at /tmp/tmp/test.nim:10 breakpoint already hit 1 time 2 breakpoint keep y 0x000055555556488a in main_Ak9bvQf5hr5bnkcYq9cDinOw at /tmp/tmp/test.nim:21 breakpoint already hit 1 time
ここに表示されている番号を delete または d コマンドに指定することによってbreakpointを削除することができます。
(gdb) delete 2 (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x0000555555564544 in foo_yBfYXi2FBVfUOybMAWEXjA_2 at /tmp/tmp/test.nim:10 breakpoint already hit 1 time
info locals コマンドで現在いるプロシージャの全ローカル変数を表示することができます。
(gdb) next 11 let y = x + 2 (gdb) next 12 return y * 10 (gdb) info locals result = 0 y = 5 TM_ipcYmBC9bj9a1BW35ABoB1Kw_6 = 5 TM_ipcYmBC9bj9a1BW35ABoB1Kw_7 = 93824992304216 FR_ = {prev = 0x7fffffffdc20, procname = 0x555555565d58 "foo", line = 11, filename = 0x555555565d5c "test.nim", len = 0, calldepth = 3} (gdb) finish Run till exit from #0 foo_yBfYXi2FBVfUOybMAWEXjA_2 (x=3) at /tmp/tmp/test.nim:12 0x0000555555564697 in bar_yBfYXi2FBVfUOybMAWEXjA (x=3) at /tmp/tmp/test.nim:16 16 return foo(x) Value returned is $2 = 50 (gdb) finish Run till exit from #0 0x0000555555564697 in bar_yBfYXi2FBVfUOy bMAWEXjA (x=3) at /tmp/tmp/test.nim:16 0x000055555556496c in main_Ak9bvQf5hr5bnkcYq9cDinOw () at /tmp/tmp/test.nim:26 26 a = bar(3) Value returned is $3 = 50 (gdb) next 27 let tobj = initTestObj(11) (gdb) next 28 (gdb) info locals a = 50 TM_ipcYmBC9bj9a1BW35ABoB1Kw_2 = 4 str = "foobar" seq1 = seq(5, 5) = {0, 1, 2, 3, 4} tobj = {num = 11, val = 3.141, str = "TestObj"} FR_ = {prev = 0x7fffffffdce0, procname = 0x555555565d75 "main", line = 27, filename = 0x555555565d5c "test.nim", len = 0, calldepth = 1}
(Windowsでは使えないっぽい)
GDBでText User Interface(TUI) modeにすると画面を分割してソースコード、アセンブリ言語、レジスタの値を表示できるようになります。 tui enable コマンドでTUI modeになり、 tui disable で元のモードに戻ります。 Ctrl-aキーを押した後にaキーを押すことでもTUI modeを切り替えられます。 GDB起動時に -tui オプションを指定するとTUI modeがデフォルトになります。
画面が乱れたときは Ctrl + L キーで画面をリフレッシュできます。
以下のコードを test2.nim に保存しGDBでデバッグしてみます。
import os, strutils proc main = let params = commandLineParams() if params.len == 0: return let count = parseInt(params[0]) var x = 0 var sum = 0 for i in 0..count: inc sum if sum == 10: inc x echo sum main()
デバッグするプログラムに引数を与えるときはnim-gdbを起動するときに --args を指定し実行ファイル名の後に引数を指定します。
nim-gdb --args test2 1000 foo (gdb) break test2.nim:11 Breakpoint 1 at 0x11b35: file /tmp/tmp/test2.nim, line 11. (gdb) run Starting program: /tmp/tmp/test2 1000 foo [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Breakpoint 1, main_3iLAKFrCD49cjgkzKbLZt2A () at /tmp/tmp/test2.nim:11 11 var sum = 0 (gdb) print params $1 = seq(2, 2) = {"1000", "foo"}
run コマンドに引数を指定することもできます。
nim-gdb test2 (gdb) break test2.nim:11 Breakpoint 1 at 0x11b35: file /tmp/tmp/test2.nim, line 11. (gdb) run 1000 foo Starting program: /tmp/tmp/test2 1000 foo [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Breakpoint 1, main_3iLAKFrCD49cjgkzKbLZt2A () at /tmp/tmp/test2.nim:11 11 var sum = 0 (gdb) print params $1 = seq(2, 2) = {"1000", "foo"}
Watchpointを使うことによって指定した変数の値が変化したときにプログラムを停止させることができます。
(gdb) watch x Hardware watchpoint 2: x (gdb) continue Continuing. Hardware watchpoint 2: x Old value = 0 New value = 1 0x0000555555565c3b in main_3iLAKFrCD49cjgkzKbLZt2A () at /tmp/tmp/test2.nim:15 15 inc x (gdb) print sum $1 = 10 (gdb) print x $2 = 1
print コマンドで変数の値を変更することができます。
(gdb) print sum = 0 $3 = 0 (gdb) print sum $4 = 0 (gdb) continue Continuing. Hardware watchpoint 2: x Old value = 1 New value = 2 0x0000555555565c3b in main_3iLAKFrCD49cjgkzKbLZt2A () at /tmp/tmp/test2.nim:15 15 inc x (gdb) print sum $5 = 10 (gdb) print x $6 = 2
until または u コマンドで一行だけ実行することができますが、ループがある場合はループが終わるまで実行されます。
(gdb) until 2166 inc(res) (gdb) until 2164 while res <= int(b): (gdb) until 17 echo sum
NimコンパイラはNim言語で書かれNimコンパイラでコンパイルされます。なのでnim-gdbでデバッグできます。 まずは普通にNim compilerをビルドします。
git clone https://github.com/nim-lang/Nim.git cd Nim
On linux:
sh build_all.sh
On windows:
build_all.bat
Nimコンパイラをデバッグ用にコンパイルします。 bin ディレクトリに nim_temp が出力されます。
koch temp
nim_temp をデバッグします。
nim-gdb --args bin/nim_temp c ../test.nim
skip
このコマンドを使うと step コマンドを使ったときなどに指定したプロシージャに入らないようにできます。 -file や -gfile オプションでソースコードを指定するとそこで定義されているすべてのプロシージャがスキップされるようになります。 例えば以下のコマンドで system モジュールにあるプロシージャがスキップされるようになります。
skip -gfile lib/system.nim skip -gfile lib/system/*.nim
rbreak regex
このコマンドを使うと正規表現 regex に名前がマッチするすべてのプロシージャにbreakpointを設定します。
save breakpoints filename
設定されているすべてのbreakpointを filename に保存します。 後で source コマンドを使って 読み込むことができます。
by Tomohiro