2006年1月 8日

C++ のシンボルをデマングルする

C++ コンパイラはシンボルが一意の名前を持つように名前マングル (name mangling) と呼ばれる処理を行います。本記事では GNU の開発環境で C++ のシンボルをデマングル (demangle) する方法を紹介します。

 

マングルの方法はコンパイラ依存です。同じコンパイラでもバージョンによってマングルの方法が異なることがあります。たとえば GCC 3.x では int foo(int) を _Z3fooi に、 int foo(const char*) を _Z3fooPKc のようにマングルしますが、 GCC 2.95 ではそれぞれ foo__FPCc, foo__Fi となります。

コマンドラインからデマングル

C++ のオブジェクトファイルに nm をかけると、デフォルトではマングルされた読みづらい形式でシンボルが出力されます。

% nm foo.o
00000000 T _Z3fooi

これを読みやすくするにはパイプで c++filt を用いるか、nm に --demangle オプションを渡します。c++filt は nm の出力に限らず、汎用的なフィルタとして使えるので便利です。

% nm  foo.o | c++filt
00000000 T foo(int)

% nm --demangle foo.o
00000000 T foo(int)

C++ でデマングル

C++ のプログラムの中からデマングルするには 2つの方法があります。1つは binutils の libiberty に含まれる cplus_demangle() を使う方法、もう1つは libstdc++ に含まれる abi::__cxa_demangle() を使う方法です。後者が使える環境であれば後者を使う方がいいでしょう。

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
using namespace std;

// From binutils/include/demangle.h
#define DMGL_PARAMS      (1 << 0)  /* Include function args */
#define DMGL_ANSI        (1 << 1)  /* Include const, volatile, etc */
#define DMGL_VERBOSE     (1 << 3)  /* Include implementation details.  */
#define DMGL_TYPES       (1 << 4)  /* Also try to demangle type encodings. */
extern "C" char *cplus_demangle(const char *mangled, int options);


int main() {
    // using libiberty
    int options = DMGL_PARAMS | DMGL_ANSI | DMGL_TYPES;
    cout << cplus_demangle("_Z3fooPKc",  options) << endl;
    cout << cplus_demangle("i",  options) << endl;

    // using libstdc++
    int status;
    cout << abi::__cxa_demangle("_Z3fooPKc", 0, 0, &status) << endl;
    cout << abi::__cxa_demangle("i", 0, 0, &status) << endl;
    return 0;
}

実行結果は次のようになります。

% g++ test.cpp -liberty && ./a.out
foo(char const*)
int
foo(char const*)
int

cplus_demangle() は libiberty.a に含まれていますが、 libiberty.h に宣言されていないので、 binutils/include/demangle.h から必要な宣言をコピーして使う必要があります。また、 libiberty.a をリンクする必要もあります。なお、cplus_demangle() は malloc したメモリを返すため、上のコードにはメモリリークあります。

abi::__cxa_demangle() は cxxabi.h をインクルードすれば使えますが、GCC 2.95 など、古い GCC には含まれていないのが難点です。abi::__cxa_demangle() のコードは binutils から GCC に取り入れられているため、処理の中身はほぼ同じと考えていいと思います。なお、abi::__cxa_demangle(mangled, 0, 0, &status) のように呼び出した場合、結果は malloc したメモリで返されるため、上のコードにはメモリリークがあります。

typeid と連携させる

C++ では typeid 演算子を用いて実行時に型の情報を得ることができます。さらに、 typeid() が返す type_info オブジェクト (のリファレンス) に対して name() メンバ関数を呼ぶと、型の名前を文字列で得られます。

GCC (少なくとも 3.3 では) では、この型の名前がマングルされているので、読みやすい形式にするにはデマングルする必要があります。

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
using namespace std;
struct Foo {
    virtual ~Foo() {};
};
struct Bar : public Foo {
    virtual ~Bar() {};
};

// memory leaks
char* demangle(const char *demangle) {
    int status;
    return abi::__cxa_demangle(demangle, 0, 0, &status);
}

int main() {
    Bar bar;
    Foo *p = &bar;
    cout << typeid(int).name() << endl;
    cout << typeid(Foo).name() << endl;
    cout << typeid(p).name() << endl;
    cout << typeid(*p).name() << endl;
    cout << endl;

    cout << demangle(typeid(int).name()) << endl;
    cout << demangle(typeid(Foo).name()) << endl;
    cout << demangle(typeid(p).name()) << endl;
    cout << demangle(typeid(*p).name()) << endl;
    return 0;
}

実行結果は次のようになります。

% g++ test.cpp && ./a.out
i
3Foo
P3Foo
3Bar

int
Foo
Foo*
Bar

Foo と Bar に仮想デストラクタをつけているのは、仮想関数が1つもないと typeid(*p) == typeid(Foo) となったためです。

まとめ

C++ のシンボルをデマングルする方法として c++filt, nm --demangle, cplus_demangle(), abi::__cxa_demangle() を紹介しました。