はじめに
D言語のGDBサポートはGNU D コンパイラ開発者のIain Buclaw氏によって2014年頃からなされてきました。
別にGDB側でサポートしなくてもまったく使えないということはないのですが、いろいろ便利になることもあります。
動作環境
- Dコンパイラ (DMD / GDC)
$ dmd --version
DMD64 D Compiler v2.084.0
Copyright (C) 1999-2018 by The D Language Foundation, All Rights Reserved written by Walter Bright
$ gdc --version
gdc (Ubuntu 8.2.0-1ubuntu2~18.04) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- GDB
GDBはdistroが提供してるやつを使います。
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
- OS
$ uname -mrv
4.15.0-43-generic #46-Ubuntu SMP Thu Dec 6 14:45:28 UTC 2018 x86_64
$ cat /etc/issue
Ubuntu 18.04.1 LTS \n \l
書かないこと
一般的なGDBの使い方については特に説明しません(するかもしれない)。
- backtrace
- breakpoint
- continue
- delete
- disassemble
- finish
- info
- next
- ptype
- rbreak
- record
- reverse-next
- reverse-step
- run
- start
- step
- stepi
- watch
あたりは知っておくとスムーズに操作できるでしょう。
使い方
GDBの基本
Hands-on形式で実際にDのプログラムをデバッグしながらGDBの使い方をみていきましょう。
てはじめに簡単なプログラムを用意します。
int fib(int n)
{
if (n < 2) return n;
else return fib(n - 1) + fib(n - 2);
}
void main()
{
auto result = fib(10);
assert(result == 55);
}
まずなによりも先にプログラムをデバッグ情報つきでビルドすることに注意をしてください。
デバッグ情報がなくてもGDBを使うこと自体は可能ですが、元のソースコード・シンボル情報・行情報はじめ多くの情報を失った状態でデバッグすることになります。
DMD/GDCともにコマンドライン引数に -g
を渡すことでデバッグ情報つきでバイナリを生成することが可能です。
$ dmd -g fibonacci.d
実際にデバッグ情報つきでビルドされているか確認するには file
コマンドが便利です。 with debug_info
が表示されているかで確認できます。
$ file fibonacci
fibonacci: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=511fc1f6afbf4bb6bd93ba5e3d05da19edd82710, with debug_info, not stripped
GDBを起動してデバッグシンボルつきのバイナリを読み込みます。
Emacsなら M-x gdb
でコマンドを実行しミニバッファに起動オプションを渡します。
私はだいたい gdb -i=mi -n -q <実行ファイル>
を、実行ファイルが引数を要求する場合は gdb -i=mi -n -q --args <実行ファイル> <引数1> <引数2>...
を渡すくらいです。だいたいGDBのコマンドラインと同じと思って差し支えありません。
普通はデバッグ情報から DW_AT_language
を読み取って自動でlanguageの設定をやってくれるのだけど、 set langauge
で明示的に指定することも可能です。
(gdb) show language
The current language is "auto; currently d".
(gdb) set language d
(gdb) show language
The current language is "d".
Cのプログラムから生成されたバイナリで start
を使うと main
関数までで実行を中断しますが、 language d
が設定されている場合は D main
まですすんでから中断します。
行情報やソースコードの情報が取得できていることが確認できます。
(gdb) start
Temporary breakpoint 1 at 0x32e88: file fibonacci.d, line 9.
Starting program: /home/kubo39/dev/dlang/fibonacci
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 1, D main () at fibonacci.d:9
9 auto result = fib(10);
step
でfib関数の中へ入ってみましょう。
ちゃんと関数の中に入っていることが確認できます。
(gdb) step
fibonacci.fib(int) (n=10) at fibonacci.d:3
3 if (n < 2) return n;
next
や finish
もちゃんと動くことが確認できます。
(gdb) next
4 else return fib(n - 1) + fib(n - 2);
(gdb) finish
Run till exit from #0 fibonacci.fib(int) (n=10) at fibonacci.d:4
0x0000555555586e92 in D main () at fibonacci.d:9
9 auto result = fib(10);
Value returned is $1 = 55
おっと、ちょっといろいろはしょよりすぎてしまいました。
reverse-next
を使って巻き戻してみましょう。
(gdb) reverse-next
Target multi-thread does not support this command.
(gdb) print $eax
$2 = 55
あらら、Dのランタイムでは特にスレッドを生成していないのですがデフォルトの状態では libpthread.so
とリンクしてしまうためにそのままでは使えません。
今回は小さいプログラムなので run
して start
しなおせばよいですが、大きいプログラムの場合はこまめに breakpoint
を設定するか record
を設定しておいたほうがよさそうです。
- breakpointの場合
(gdb) break fibonacci.d:fibonacci.fib(int)
Breakpoint 3 at 0x555555586e47: file fibonacci.d, line 3.
(gdb) r
Starting program: /home/kubo39/dev/dlang/fibonacci
[Switching to thread 1 (process 6888)](running)
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 3, fibonacci.fib(int) (n=10) at fibonacci.d:3
3 if (n < 2) return n;
(gdb) backtrace
#0 fibonacci.fib(int) (n=10) at fibonacci.d:3
#1 0x0000555555586e92 in D main () at fibonacci.d:9
- recordの場合
(gdb) start
Temporary breakpoint 1 at 0x32e88: file fibonacci.d, line 9.
Starting program: /home/kubo39/dev/dlang/fibonacci
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 1, D main () at fibonacci.d:9
9 auto result = fib(10);
(gdb) record
(gdb) next
10 assert(result == 55);
(gdb) reverse-next
fibonacci.fib(int) (n=2) at fibonacci.d:4
4 else return fib(n - 1) + fib(n - 2);
式
式評価も可能ですが、 等価に表現できないことに注意してください。
GDB内ではyaccを使ってDのサブセットをパースしています。
- ふつうにできるケース (castとかpow演算子とかsizeofとかtypeofとか)
(gdb) print cast(ubyte) -1
$1 = 255
(gdb) print 2 ^^ 2
$2 = 4
(gdb) print int.sizeof
$3 = 4
(gdb) ptype typeof(1.0)
type = double
- かっこならいけるケース (値に対するsizeof)
(gdb) print 4.sizeof
Invalid number "4.sizeof".
(gdb) print (4).sizeof
$5 = 4
- ぜんぜんだめなケース (連想配列リテラルとかレンジとか)
(gdb) print [1:1]
A syntax error in expression, near `:1]'.
(gdb) print ([1:1])
A syntax error in expression, near `:1])'.
(gdb) print "hoge"[1 .. $]
A syntax error in expression, near `. $]'.
(gdb) print ("hoge"[1 .. $])
A syntax error in expression, near `. $])'.
(gdb) print 0..3
A syntax error in expression, near `..3'.
(gdb) print (0..3)
A syntax error in expression, near `..3)'.
型
いくつかのプリミティブな型は ptype
を使うことででD固有の型情報を取得できます。
たとえば以下のように整数リテラルに _
が入っていたりサフィックスに UL
をつけているものを判定できます。
(gdb) ptype 0x0_1
type = int
(gdb) ptype 0UL
type = ulong
delegateなどはCのstruct扱いになっています。
(gdb) ptype dg
type = volatile struct _Delegate {
void *ctxptr;
void (*funcptr)(void);
}
watch
普通のGDBの使い方ではありますが、これもD言語対応してる機能です。
まず以下のようなコードを用意してコンパイルしておきます。
void main()
{
int y = 7;
y++;
y++;
}
watch -location
で変数を監視して内容が変わったら停止する、といった使い方をします。
breakpointと違って変化した後に止まるので注意してください。
(gdb) start
Temporary breakpoint 1 at 0x32de4: file watch.d, line 3.
Starting program: /home/kubo39/dev/dlang/watch
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 1, D main () at watch.d:3
3 int y = 7;
(gdb) watch -location y
Hardware watchpoint 2: -location y
(gdb) info watchpoints
Num Type Disp Enb Address What
2 hw watchpoint keep y -location y
(gdb) continue
Continuing.
Hardware watchpoint 2: -location y
Old value = -9424
New value = 7
D main () at watch.d:4
4 y++;
(gdb) continue
Continuing.
Hardware watchpoint 2: -location y
Old value = 7
New value = 8
D main () at watch.d:5
5 y++;
demangle
GDBではdemangleもサポートしています。
通常作業言語をDに選択していればデフォルトでdemangle機能がはたらきます。
(gdb) demangle _Dmain
D main
GDBのdemangleは DMD 2.077以降の新しいマングリングルール に準拠していないので新しめのDMDで生成したバイナリだとdemangleできないものもあります。
例えば後方参照を含むシンボルはdemangleできません。これにかんしては現在作業中といったステータスです
あとちんまい問題だとObjective-C linkage属性な関数とvariadic argumentsの組み合わせもできません。
(gdb) bt
#0 _D9scopesegv3barFNaNbNfxDFNfZvZxQi (dg=...) at scopesegv.d:10
#1 0x0000555555586fe9 in D main () at scopesegv.d:15
(gdb) demangle _D9scopesegv3barFNaNbNfxDFNfZvZxQi
Can't demangle "_D9scopesegv3barFNaNbNfxDFNfZvZxQi"
GDCでは古いマングリングルールなので正確にdemangleできています。
(gdb) bt
#0 scopesegv.bar(const(void() @safe delegate)) (dg=...) at scopesegv.d:7
#1 0x00005555555548e2 in D main () at scopesegv.d:15
(gdb) set print demangle off
(gdb) bt
#0 _D9scopesegv3barFNaNbNfxDFNfZvZxDFNfZv (dg=...) at scopesegv.d:7
#1 0x00005555555548e2 in _Dmain () at scopesegv.d:15
(gdb) demangle _D9scopesegv3barFNaNbNfxDFNfZvZxDFNfZv
scopesegv.bar(const(void() @safe delegate))
demangleのコードはGDBとbinutilsで共通なので, addr2line
, c++filt
, nm
, objdump
といったツール群でもdemangleが可能です。
ただし現時点でこれらのツールは -C
や --demangle=auto
では勝手にdemangeしてくれないので、
- addr2line, nm, objdump:
--demangle=dlang
を使う - c++filt:
-s dlang
もしくは--format=dlang
を使う
という感じで明示的にオプションを渡す必要があります。
DMDでうまくいかないんだけど...という場合は ddemangle
というツールを使うとだいたい最新のマングリングルールに追従しているはずなのでうまいことdemangleできるかもしれません。投稿時点ではObjective-C linkage属性な関数はできなかったりしますが。。
コンパイラによってmanglingが変わる場合に生成したバイナリがどのコンパイラでコンパイルされたか知りたい、という状況もあります。
ちゃんとしたコンパイラならDWARFの DW_AT_producer
というセクションにどのコンパイラで生成されたのかという情報が作成され、 readelf -wi
もしくは objdump -g
を使って確認ができます。
$ readelf -wi gdc.bin| grep DW_AT_producer
<c> DW_AT_producer : (indirect string, offset: 0x1a0): GNU D 8.2.0 -mtune=generic -march=x86-64 -g
$ readelf -wi dmd.bin| grep DW_AT_producer
<c> DW_AT_producer : Digital Mars D v2.083 # これは単にDMDのバグ
おわりに
いかがでしたか?ここまでお読みいただきありがとうございました。