gdb で golang のソースコードデバッグが出来ないと思ったらアホな理由だった.

gdb で C++ のソースをデバッグするときに 「b main」からの「r」をあまりに打ちすぎているが故に go でも無意識に同じ事やってしまい「あれ? hit したのに list でディスアセンブルしかできない!!」と暫くハマってしまいました…

--- 省略 ---
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
info "(gdb)Auto-loading safe path"
(gdb) b main
Breakpoint 1 at 0x456300: file /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s, line 67.
(gdb) r
Starting program: /home/*******/Workspace/go/src/local/misc/misc

Breakpoint 1, main () at /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s:67
67              MOVQ    $runtime·rt0_go(SB), AX
(gdb) list
62      GLOBL _rt0_amd64_linux_lib_argc<>(SB),NOPTR, $8
63      DATA _rt0_amd64_linux_lib_argv<>(SB)/8, $0
64      GLOBL _rt0_amd64_linux_lib_argv<>(SB),NOPTR, $8
65
66      TEXT main(SB),NOSPLIT,$-8
67              MOVQ    $runtime·rt0_go(SB), AX
68              JMP     AX
(gdb)

なんて事はありません.  go の場合、func main のシンボルは main.main  ([package name].[function name]) なので breakpoint を set するには b main.main としなければならないと言うだけでした… なんか、

warning: File "/usr/share/go-1.6/src/runtime/runtime-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
add-auto-load-safe-path /usr/share/go-1.6/src/runtime/runtime-gdb.py

みたいな warning が出てたので言われるがままに  .gdbinit に追加してみたり、デバッグ情報が入っていないのか?と思って go build の引数いじってみたりしてしまいましたが、全然関係ありませんでした.

分かった後によくよく見てみれば、自分の書いたコードではなく lib の中身に set されているじゃないかという.

Breakpoint 1 at 0x456300: file /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s, line 67.

一発 nm &  grep main でもすれば直ぐに気づきそうなものの、いつもの流れで下手に break 張れて hit までしてしまったので、完全に思考停止してしまっていました.

つくづくアホな事で時間を使ってしまいました (反省)

広告

Golang で C++ のクラスを bind する.

C++ で書かれた資産を別言語から利用したというケースは結構あると思いますが、C++ で書かれたコードはシンボルへの名前修飾などの問題があり(問題はそれだけでもという訳ではないけれど)、他言語から直接 C++ のコードを呼び出すというのは簡単には出来ないのが一般的です.

マングリングされたシンボル名を直接指定するとかの力技は置いておいて、この場合一般的にどうするかと言うと実現したい機能の入り口となる C linkage の関数(簡単に言うと C++ で extern “C” をつけた関数)をインターフェースとして用意し、実装ファイルの方で C++ の関数なり、クラスなどを組み合わせて利用して所望の機能を実装するという事が多いと思います.

イメージとしては GoF のファサードパターンのような感じです.

ただこの方法だと裏側の C++ のコード(特にクラス)が持っているインターフェースの詳細が隠蔽されてしまうので、C++のものをそのまま移植したような、整合性のあるインターフェースを持ったクラスを他言語でも利用したいといったケースには対応することができません.

ではこういう場合どうするか.  ポイントは間に横たわる C言語の障壁を突破して如何に以下の 2 点を実現するかにあります.

(1)  Cの障壁を跨いでクラスのインスタンスの情報をどうやり取りするか.
(2) メンバー関数の引数や戻り値に含まれる non-POD (C言語で扱えない)なデータをどうやり取りするか.

これらの具体的な対処法ですが、(1) については C 標準ライブラリのファイルディスクリプタのようにコンテキスト(この場合は bind したいクラスの this の情報) をコンテキストの生成を行う関数から返却し、それを外部で引き回して他の関数に渡して使用するという方法で実現することができます.  また、(2) については bind したいクラスと同じ名前の関数を持ち、かつ入出力が POD オンリーな wrapper クラスを作成し、wrapper から元のクラスに処理を移譲するようにすることで実現する事ができます.  (もし入出力が POD に変換し辛いクラスの場合、そのクラスを先に bind してそのコンテキストを入出力として扱う事で同じく対応する事が可能)

言葉だけの説明ではアレですのでとりあえず以下を御覧ください. (Foo という std::pair<std::string, std::string> の setter / getter を持つ単純なクラスを cgo で bind してみた例です)

https://github.com/cutlassfish/cgo_bind

・Foo
bind 対象のクラス.

・FooWrapper
Foo を wrap し、入出力を POD 型へ(ここでは std::pair<std::string, std::string> を const char* をメンバーに持つ pair 構造体へ)変換するクラス.

・foo_c
FooWrapper の生成および破棄と、呼び出し元から渡されたコンテキストの情報を元に 適切な FooWrapper のインスタンスに呼び出しを移譲する C linkage のインターフェース.

ここでは  FooWrapper のオブジェクトが配置されている仮想メモリー空間のアドレスをコンテキスト(厳密に言うとコンテキスト識別子)として Foo_construct() から返却しています.  (生存期間にあるオブジェクトのアドレスは一意である事が保証されているため、自前で識別子を用意せずとも new から返却されたアドレス自体を識別子として利用することができます)  呼び出し時は go 側から渡された数値としてのアドレスを foo_c 内の関数で FooWrapper 型のポインターにキャストし、FooWrapper 経由で元の Foo へ処理を移譲しています.

このような単純な例だと FooWrapper のような物を用意するのは冗長に思えるかもしれませんが、bind 対象のクラスのメンバー関数が増えてくると POD <–> non POD の変換に複数の状態が絡んでくる事になるので、よほど単純な場合以外はメンテナンス性の観点から wrapper を用意して局所化を行っておくのが良いと思います .

今回  golang へ bind と言うことでやってみましたが、基本的な考え方は他の言語で bind する場合にも応用できるはずです.