LoginSignup
2
0

More than 3 years have passed since last update.

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

Posted at

結論

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

set print object on

gdb manual

背景

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

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

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

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でデバッグすると、デフォルトでは下記のような出力が得られます。

(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クラスのポインタにキャストする必要があります。

(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型として解釈してくれます。

(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コンテナに保持したポインタの先を、実際のクラスとして解釈して出力することができます。

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
(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
}

環境

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

$ 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及びサンプルコードはこちらにあげています。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0