gdbでポリモーフィズムにおける実際のクラス情報を取得する

# 結論

C++をgdbでデバッグする時(特にポリモーフィズム使ってる場合）は、下記の設定をするとわかりやすいです。

```
set print object on
```

[gdb manual](https://sourceware.org/gdb/onlinedocs/gdb/Print-Settings.html)


# 背景

C++でポリモーフィズムすると、デバッグ時に実際のクラスがぱっと見わからなくて困ることがあります。

例として、以下のようなプログラムを考えます。

`Animal`クラスを基底クラスとして、そこから継承したクラスをいくつか定義しています。
それらのインスタンスへのポインタを`std::vector`でまとめて、
`Chorus`関数にてポリモーフィズムを使った`MakeSound`呼出をしています。

```c++:animal.cc
#include <iostream>
#include <memory>
#include <vector>

class Animal {
 public:
  virtual ~Animal() {}
  virtual void MakeSound() const { std::cout << "#!@" << std::endl; }
};

class Cat : public Animal {
 public:
  virtual ~Cat() {}
  virtual void MakeSound() const { std::cout << "Meow" << std::endl; }
};

class Dog : public Animal {
 public:
  virtual ~Dog() {}
  virtual void MakeSound() const { std::cout << "Bow" << std::endl; }
};

class Human : public Animal {
 public:
  explicit Human(int age) : age_(age) {}
  virtual ~Human() {}
  virtual void MakeSound() const { std::cout << "Hello" << std::endl; }
private:
  int age_;
};

class Baby : public Human {
 public:
  explicit Baby(int age, bool can_stand) : Human(age), can_stand_(can_stand) {}
  virtual ~Baby() {}
  virtual void MakeSound() const { std::cout << "Baboo" << std::endl; }
private:
  bool can_stand_;
};

static void Chorus(const std::vector<std::unique_ptr<Animal>>& animals) {
  for (const auto& animal : animals) {
    animal->MakeSound();
  }
}

int main() {
  std::vector<std::unique_ptr<Animal>> animals;
  animals.push_back(std::make_unique<Animal>());
  animals.push_back(std::make_unique<Cat>());
  animals.push_back(std::make_unique<Dog>());
  animals.push_back(std::make_unique<Human>(20));
  animals.push_back(std::make_unique<Baby>(3, true));

  Chorus(animals);
  return 0;
}
```

この`Chorus`関数をgdbでデバッグすると、デフォルトでは下記のような出力が得られます。

```c++
(gdb) b Chorus(std::vector<std::unique_ptr<Animal, std::default_delete<Animal> >, std::allocator<std::unique_ptr<Animal, std::default_delete<Animal> > > > const&)
Breakpoint 1 at 0x1076: file animal.cc, line 41.
(gdb) r
Starting program: /home/gdb_macro/sample/animal 

Breakpoint 1, Chorus (animals=std::vector of length 5, capacity 8 = {...}) at animal.cc:41
41	static void Chorus(const std::vector<std::unique_ptr<Animal>>& animals) {
(gdb) p animals
$1 = std::vector of length 5, capacity 8 = {std::unique_ptr<Animal> = {
    get() = 0x55555576ee70
  }, std::unique_ptr<Animal> = {
    get() = 0x55555576eeb0
  }, std::unique_ptr<Animal> = {
    get() = 0x55555576ee90
  }, std::unique_ptr<Animal> = {
    get() = 0x55555576eed0
  }, std::unique_ptr<Animal> = {
    get() = 0x55555576ef20
  }}
(gdb) p *animals[4]
$2 = {
  _vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
}
```

`animals`の出力からは、`Animal`への`std::unique_ptr`ということしかわかりません。
そこで、要素のポインタが指す先を確認したところ、
vtableのアドレスから`Baby`クラスのインスタンスっぽいことはわかりますが、
`Baby`クラスが持つメンバ変数などの情報は表示されません。
表示するためには、下記のように一旦`Baby`クラスのポインタにキャストする必要があります。

```c++
(gdb) p *(Baby*)animals[4]
$3 = {
  <Human> = {
    <Animal> = {
      _vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
    }, 
    members of Human: 
    age_ = 3
  }, 
  members of Baby: 
  can_stand_ = true
}
```

# 解決策

このキャストのように、実際のクラスとして解釈し出力するようにするgdbの設定が、以下です。

```
set print object on
```

この設定を行うと、キャストなしで`Baby`型として解釈してくれます。

```c++
(gdb) set print object on
(gdb) p *animals[4]
$11 = (Baby) {
  <Human> = {
    <Animal> = {
      _vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
    }, 
    members of Human: 
    age_ = 3
  }, 
  members of Baby: 
  can_stand_ = true
}
```

# 応用

さらに、以下のようなgdb macroを定義しておくと、
STLコンテナに保持したポインタの先を、実際のクラスとして解釈して出力することができます。

```shell:print_each_pointee.gdb
define print_each_pointee
  if $argc == 0
    help print_each_pointee
  else
    set $size = $arg0.size()
    set $i = 0
    while $i < $size
      printf "$arg0[%u]: ", $i
      p *$arg0[$i]
      set $i++
    end
  end
end

document print_each_pointee
  Prints each pointee of STL container member
  Syntax: print_each_pointee <container>
end

set print pretty on
set print object on
```

```c++
(gdb) source print_each_pointee.gdb
(gdb) print_each_pointee animals
animals[0]: $12 = (Animal) {
  _vptr.Animal = 0x55555575bca8 <vtable for Animal+16>
}
animals[1]: $13 = (Cat) {
  <Animal> = {
    _vptr.Animal = 0x55555575bc80 <vtable for Cat+16>
  }, <No data fields>}
animals[2]: $14 = (Dog) {
  <Animal> = {
    _vptr.Animal = 0x55555575bc58 <vtable for Dog+16>
  }, <No data fields>}
animals[3]: $15 = (Human) {
  <Animal> = {
    _vptr.Animal = 0x55555575bc30 <vtable for Human+16>
  }, 
  members of Human: 
  age_ = 20
}
animals[4]: $16 = (Baby) {
  <Human> = {
    <Animal> = {
      _vptr.Animal = 0x55555575bc08 <vtable for Baby+16>
    }, 
    members of Human: 
    age_ = 3
  }, 
  members of Baby: 
  can_stand_ = true
}
```

# 環境

以下の環境で動作確認しています。

```shell
$ uname -r
5.5.8
$ lsb_release -d
Description:	Ubuntu 18.04.5 LTS
$ gdb --version | head -1
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
$ gcc --version | head -1
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
```

gdb macro及びサンプルコードは[こちら](https://github.com/takeoverjp/gdb_macro)にあげています。

# 参考

- [Print Settings (Debugging with GDB)](https://sourceware.org/gdb/onlinedocs/gdb/Print-Settings.html)
- [STLSupport - GDB Wiki](https://sourceware.org/gdb/wiki/STLSupport)