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 までしてしまったので、完全に思考停止してしまっていました.

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

広告

C/C++ で配列の内容を関数として実行する.

OSなし環境とかRTOS環境下ではちょくちょくやってましたが、Linux とかではそういえばやったことないなと思い、やってみました.

Segmantation Fault を出しながら、試行錯誤して一応できたのが以下.

※ (1)
mprotect で指定するアドレスは page size 境界にある必要があるので、そのための attribute.

※ (2)
関数のバイナリを含む配列を .text 領域に配置するための attribute. 最初 .bss セクションにおいてやってみたけれど、どうも .bss だと mprotect で PROT_EXEC を付与してもSegmantation Fault が出るのようなので指定.  なんで出来ないのか… linker script とか Intel のアーキテクチャマニュアルとか、詳解 Linux kernel とか、そのへん読み解いたら分かるかもしれませんが、とりあえず今回は .text に配置.

※ (3)
.text 領域はデフォルトで書き込み権限がないため、mprotect で書き込み権限を付与.

※ (4)
配列内に機械語でバイナリーを直接プログラミングするのがしんどかったので、double の乗算を行う関数 mul を buf にコピー (サイズに指定している 128 は適当で多分余分なのもコピーされている)  今回は単純な乗算なので問題ないけれど、相対アドレッシングで外に分岐したり外のデータにアクセスするような関数の機械語をコピーしても変な所にアクセスして期待した動作はしないのでその辺りは注意.

※(5)
buf の先頭を関数ポインター (double(*)(double, doble)) にキャスト.

※(6)
関数ポインタ fp 経由で buf の内容をコードとして実行.

さてこのソースを make してみると…

g++  -g -Wall -Wextra -c main.cpp
/tmp/ccxKSKnq.s: Assembler messages:
/tmp/ccxKSKnq.s:35: Warning: ignoring changed section attributes for .text
g++ main.o -o main

のようになにやら  warning がでていますが、実行した結果は以下のようになっており、buf は  .text 領域に配置され、コピーされた mul の内容も関数として正しく動作しているようです.

./main
0x402000
0x403000
6.000000

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 する場合にも応用できるはずです.

template クラスを private メンバーに含むクラスを DLL 化した時に出る C4251 warning について.

template クラスを private メンバーに含むクラスを DLL 化した際に「class ‘XXXXXX’ は __export キーワードを使って class ‘YYYYYY’ にエクスポートしてください」のような warning に出くわした方は多いと思います.

何故、template の export をしろと言われてしまうのか… 公式のドキュメントにもその理由についてははっきり書かれてはいないのであくまで推測になってしまいますが、このケース(正確には template メンバーが private、 inline での使用なし、メンバー関数の入出力に関係なし)に限ってはMSVC の処理系の都合で warning が出ていると思われます.

そもそも template のインスタンスの export が必要なのは「template のコードは翻訳単位ごとにインスタンス化される」と言う処理系の template サポートの方法と、実行時までどのようなコードがリンクされるか分からないと言う DLL の性質によるものと思われます. (MSVCがどのような実装になっているかは定かではないですが、恐らく例外ではないかと…  詳細は本の虫を参照)

仮に export を行わなかった場合、DLL 側が保持している template のインスタンスとクライアント側の template のインスタンスがシンボル的には一致しているにも関わらず処理内容が異なると言う事がありえますし、実際にそのような仕様になっているかは別として、DLL とクライアント側でコンパイルに使用された処理系のバージョンやオプションが異なる場合、元となった template の記述が同一であったとしても想定しているデータアライメント等が異なると言うことも起こり得ます.

一方クラスの template メンバー変数が private で inline でも使用されておらず、メンバー関数の入出力とも無縁な場合、基本的にはクライアント側でコードのインスタンス化は必要ないはずですが実際には C4251 warning が出てしまいます…   恐らく処理系の都合なのでしょうが、なぜこういったケースを検出できないのか??  template の仕様を逆手に取った private メンバーへのアクセス等まで考慮した結果なのか???

前置きが長くなってしまいました.

さて、この warning への対処方法ですが、メッセージ通りに template class のインスタンスを DLL から export する方法と合わせて代表的には以下の 3 つがあるのではないかと.

(1) #pragma で C4251 を無視する.
(2) メンバー変数として保持している template の instance を全て export する.
(3) pimpl イディオムを利用する.

(1) #pragma で C4251 を無視する.

もっとも楽な方法ですが、C4251 で警告されるのはこの template 絡みの warning のみではないようなので、DLL の危険性に関する知識がない場合 data crruption に繋がる warning を見逃してしまうかもしれません.  そうでなくても #pragma による warning 抑制はその意味と危険性を理解した上で行う最終手段的な側面があるため、むやみに使用せず他の手がないかまず考えるべきだと思います.

https://docs.microsoft.com/ja-jp/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4251

(2) メンバー変数として保持している template の instance を全て export

__declspec(dllexport) を使用してメンバー変数として保持している template のインスタンスを export する方法です.  自前で定義した単純な template の場合は問題ありませんが、STL のような複雑なものを export する場合、デフォルト指定されている template 変数 (STL コンテナのアロケーター等)や基底クラスまで遡って全てインスタンス化を行う必要があるため、中々な手間になる事があります. また std::map, std::set, std::queue, std::list, std::deque などはそもそも export 出来ないようなので注意が必要です.

https://support.microsoft.com/en-us/help/168958/how-to-export-an-instantiation-of-a-standard-template-library-stl-clas

(3) pimpl イディオムを利用する.

場合によってはアンチパターンと言われる事もありますが、pimpl をイディオムを使う事でもこの問題を回避出来ます.  デメリットはコードの記述量が増えてしまう点.

どうでしょう?  (1)(2) のに比べて危険なコードを書いてしまう可能性も低く、難しい事を考えなくても大丈夫かつ一番汎用性があるので、個人的には(3) を選ぶ事が多い気がしますが…

const でない空の std::string に対する先頭要素への添え字 (operator[])アクセスは C++11 以前は未定義

ちょっと作業をしていて、最近知った仕様.  Fix した規格書ではなく working draft からの参照になってしまいますが、多分内容は変わっていないはず.

C++ Working Draft – n1905
Returns: If pos < size(), returns *(begin() + pos ). Otherwise, if pos == size(), the const version returns charT(). Otherwise, the behavior is undefined.

C++ Working Draft – n4659
Returns: *(begin() + pos) if pos < size(). Otherwise, returns a reference to an object of type charT with value charT(), where modifying the object to any value other than charT() leads to undefined behavior.

「if pos == size() の時」と言う事で直接的にそう書いてある訳ではないですが、空の std::string の場合は size() == 0 なので結果的に str[0] は未定義になります.

古い処理系を使用していて、本当は未定義だけど気を効かせてくれて non const でも問題なしとなっている場合はあえて目くじら立てる事はないのかもしれないですが…

(2018年現在においても C++11 以前の別の処理系に乗り換える可能性があるようなケースでは別.  STL を使用しているケースは多くないと思うけど、Embedded 系は稀に良くある??)

にしても、勉強不足を痛感…

 

GNU Make を単体テストのテストランナーに使用しようとして困ったことあれこれ

make test で単体テストが実行されるようになっているプロジェクトを CI/CD に対応させる際に色々と右往左往してしまったので make マスターの面々からするとヤレヤレな内容かもしれませんが… メモがてら.

ビルドシステムに make が採用されている場合、多くの場合サブディレクトリ毎に makefile が用意されていて、それをプロジェクトのトップディレクトリに置かれた makefile から再帰 make するというのがよく行われると思います.  例えば以下のような構成のプロジェクトでは (a) から (b), (c) の makefile を再帰 make すると言った具合です.

├── Makefile          — (a)
├── subdir0
│      └── Makefile  — (b)
└── subdir1
│      └── Makefile  — (c)

また、(b) (c) の makefile には疑似 ターゲットとして test が定義されており、各々のディレクトリで make test を打つことで lib の生成や単体テストのビルドと実行が行われるようになっています.  このプロジェクトを CI/CD 対応しようとしたとき、ツール側から沢山あるサブディレクトリを一つ一つ個別に叩いて行くのは面倒なので当然 (a) に定義された test ターゲットを叩いて済ませたくなる訳ですが、単体テストの実行には一般的に以下の要求事項があり、この要求を満足する形でテストランナーを実装しなくてはなりません.

(1) 途中で fail しても後続のテストは実行したい.
(2) 全てのテストを実行し終えた後に一つでも fail していた場合に全体の終了ステータスをエラーにしたい.

さてどうしたものかと言うことで、真っ先に思いついたのが for でサブディレクトリを回す方法ですが、この方法では呼び出し元の make にエラーが帰らないため、(2) の条件を満たす事ができません.

次に思いついたのが、以下のように test ターゲットのコマンド行に再帰 make を直書きして -k (–keep-going)オプションで make を起動するという方法でしたが、-k オプションはターゲットのコマンド行で発生したエラーを無視して続行するのではなく、エラーを起こしたターゲットの構築を中断して、別のターゲットの構築を続けて実行するというオプションのようなので期待した動作をしません.  (というのをやってみて気づいた)

Normally make gives up immediately in this circumstance, returning a nonzero status. However, if the ‘-k’ or ‘–keep-going’ flag is specified, make continues to consider the other prerequisites of the pending targets, remaking them if necessary, before it gives up and returns nonzero status. For example, after an error in compiling one object file, ‘make -k’ will continue compiling other object files even though it already knows that linking them will be impossible.

https://www.gnu.org/software/make/manual/html_node/Errors.html

そうこうして最終的に「サブディレクトリ毎に test_subdir0, test_subdir1 のようなテスト専用ターゲットを (a) に用意して -k オプションを付けて叩くしかない」という結論に至ったのですが、実際はディレクトリの数も多く、ベタ書きはダサいなー嫌だなーと言うことで無い知恵絞ってヒネリだしたのが以下

なんかとりとめもなくなってしまった&もっといい方法がありそうな気がしてならなですが、とりあえず要件は満たせた!??

「コードの綺麗さ」と言う言葉に対する表面的な理解

「コードの綺麗さ」といった場合、いくつか評価軸がありますが恐らくほぼ以下の 3 つに集約できるのではないかと思います.

(1) 関数内部、ファイル内部等の局所的に記述されているコードの見た目 (改行とかスペースとか) が綺麗かどうか
(2) 関数内部、ファイル内部等の局所的に記述されている処理や記述に無駄なく美しいかどうか
(3) モジュールの切り分け、命名等を含む、大域的に見た設計が統一され美しいかどうか

もちろん全部綺麗なことに越した事はないですが、(3)が最も重要度と抽象度が高く、逆に(1)は個人的にそこまで重要では無いと思っています.

何故なら、見た目の統一感の無さよりアーキテクチャ上の統一感の無さの方が余程問題だから. どうしても見た目を揃えたいならツールにやらせれば良いことで、このような作業を人間が時間を割いてやるほどアホで愚かな事はありません.  (後れ馳せながら最近仕事で golang を触り始めたのですが、go fmt を処理系と一緒に用意して宗教戦争も含めた無駄を排除するという考え方、すごく良いなと思いました.)

ですが、現実にはコードの綺麗さを(1)でしか測れず、コードレビューの際にもこういった見た目の指摘ばかり繰り返す人間がかなりの割合でいます.  そういう人達に欠如しているのは、本質が何なのかを理解する能力や、コードを俯瞰してみる(つまり木ではなく森を見る)と言う視点です.

優れたソフトウェアほど一貫した設計思想があり、その思想を理解することで細部を読まずとも全体を想像し、構造を把握する事が出来ます.

自分は何度か転職をしていますが、一番最初と現在の会社を除いてこのような視点を持っている人間は皆無でした.

以前自チームで構築したプログラムを同僚に解説せよと上から仰せつかった際、全体の規模が大きかった事もありアーキテクチャーの解説を中心に行った所、「お前の説明はなっていない」と言わんばかりの感じで何故それが知りたいのか理解出来ない細部の説明ばかりを求められた事があります.

転職の際の引き継ぎだったので無表情で言われるがまま応じましたが、言うまでもなく死ぬほどウンザリしました.

残念な組織ほどこういった本質に対する理解力の欠如した人間が大きな顔をし、挙句の果てにコーディング規約なんか作っちゃってるからもう…