GDBでD言語で作成したバイナリをデバッグできるのか？どのようなサポートがされているのか？調べてみました！

## はじめに

D言語のGDBサポートはGNU D コンパイラ開発者のIain Buclaw氏によって2014年頃からなされてきました。
別にGDB側でサポートしなくてもまったく使えないということはないのですが、いろいろ便利になることもあります。

### 動作環境

- Dコンパイラ (DMD / GDC)

```console
$ 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が提供してるやつを使います。

```console
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

```console
$ 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
* print
* ptype
* rbreak
* record
* reverse-next
* reverse-step
* run
* start
* step
* stepi
* watch

あたりは知っておくとスムーズに操作できるでしょう。

## 使い方

### GDBの基本

Hands-on形式で実際にDのプログラムをデバッグしながらGDBの使い方をみていきましょう。

てはじめに簡単なプログラムを用意します。

```d
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` を渡すことでデバッグ情報つきでバイナリを生成することが可能です。

```console
$ dmd -g fibonacci.d
```

実際にデバッグ情報つきでビルドされているか確認するには `file` コマンドが便利です。 `with debug_info` が表示されているかで確認できます。

```console
$ 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` で明示的に指定することも可能です。

```console
(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` まですすんでから中断します。
行情報やソースコードの情報が取得できていることが確認できます。

```console
(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関数の中へ入ってみましょう。
ちゃんと関数の中に入っていることが確認できます。

```console
(gdb) step
fibonacci.fib(int) (n=10) at fibonacci.d:3
3	    if (n < 2) return n;
```

`next` や `finish` もちゃんと動くことが確認できます。

```console
(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` を使って巻き戻してみましょう。

```console
(gdb) reverse-next
Target multi-thread does not support this command.
(gdb) print $eax
$2 = 55
```

あらら、Dのランタイムでは特にスレッドを生成していないのですがデフォルトの状態では `libpthread.so` とリンクしてしまうためにそのままでは使えません。

今回は小さいプログラムなので `run` して `start` しなおせばよいですが、大きいプログラムの場合はこまめに `breakpoint` を設定するか `record` を設定しておいたほうがよさそうです。

- breakpointの場合

```console
(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の場合

```console
(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とか)

```console
(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)

```console
(gdb) print 4.sizeof
Invalid number "4.sizeof".
(gdb) print (4).sizeof
$5 = 4
```

* ぜんぜんだめなケース (連想配列リテラルとかレンジとか)

```console
(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` をつけているものを判定できます。

```console
(gdb) ptype 0x0_1
type = int
(gdb) ptype 0UL
type = ulong
```

delegateなどはCのstruct扱いになっています。

```console
(gdb) ptype dg
type = volatile struct _Delegate {
    void *ctxptr;
    void (*funcptr)(void);
}
```

### watch

普通のGDBの使い方ではありますが、これもD言語対応してる機能です。

まず以下のようなコードを用意してコンパイルしておきます。

```d
void main()
{
    int y = 7;
    y++;
    y++;
}
```

`watch -location` で変数を監視して内容が変わったら停止する、といった使い方をします。
breakpointと違って変化した後に止まるので注意してください。

```console
(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機能がはたらきます。

```console
(gdb) demangle _Dmain
D main
```

GDBのdemangleは [DMD 2.077以降の新しいマングリングルール](https://dlang.org/blog/2017/12/20/ds-newfangled-name-mangling/) に準拠していないので新しめのDMDで生成したバイナリだとdemangleできないものもあります。
例えば後方参照を含むシンボルはdemangleできません。これにかんしては[現在作業中](https://github.com/ibuclaw/gdb/pull/8)といったステータスです
あとちんまい問題だとObjective-C linkage属性な関数とvariadic argumentsの組み合わせもできません。

```console
(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できています。

```console
(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` を使って確認ができます。

```console
$ 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のバグ
```

## おわりに

いかがでしたか？ここまでお読みいただきありがとうございました。