C++で実行時エラーを追跡と特定します

Pythonを使った頃はバックトレース機能がとても強いためエラーの追跡に悩んだことはあまりありませんでした。

```python
>>> def hogehoge(): print traceback.print_stack()
... 
>>> def hoge(): hogehoge()
... 
>>> hoge()
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in hoge
  File "<stdin>", line 1, in hogehoge
None
```

しかしC++ではそんな便利なことはなかなかできません。
自分が模索してきたエラーの追跡と特定手法をここに書いておきます。
デバッグツールとMSVCを使ってる人には向けません。

##スタックトレース
コールツリーを調べるときに使えます。

以下のコードは
Windows 7 mingw-w64 i686 gcc 4.9.1
Centos 6.5 Clang 3.5
MacOSX 10.9 Clang 3.4
にてテスト済み。

```cpp:traceback.cpp
#include <iostream>
#include <sstream>
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
	#define __posix__
	#include <execinfo.h>
#elif defined(__MINGW32__)
	#include <windows.h>
#endif

/// バックトレース情報を取得します
std::string get_backtrace_info() {
	std::string result;
#if defined(__posix__)
	const static ssize_t trace_frames_max = 100;
	void* trace_frames[trace_frames_max] = {};
	int size = ::backtrace(trace_frames, trace_frames_max);
	if (size < 0 || size >= trace_frames_max)
		return std::string("get_backtrace_info error: backtrace failed");
	char** trace_symbols = ::backtrace_symbols(trace_frames, trace_frames_max);
	if (!trace_symbols)
		return std::string("get_backtrace_info error: get symbol failed");
	for (ssize_t i = 1; i < size; ++i) { // 呼び出し先のアドレスから書き込みます
		result.append(trace_symbols[i]);
		result.append("\n");
	}
	free(trace_symbols); // [pointer, pointer, char*, char*, ...]
	trace_symbols = nullptr;
#elif defined(__MINGW32__)
	const static ssize_t trace_frames_max = 50;
	void* trace_frames[trace_frames_max] = {};
	// trace_frames_maxが63以下でない場合は0しか返しません
	int size = ::CaptureStackBackTrace(0, trace_frames_max, trace_frames, nullptr);
	if (size < 0 || size >= trace_frames_max)
		return std::string("get_backtrace_info error: backtrace failed");
	// mingwはpdbを生成できないので関数の名前は取得できません
	// ここはアドレスのみを出力します
	std::ostringstream address_str;
	for (ssize_t i = 1; i < size; ++i) {
		int64_t address = reinterpret_cast<int64_t>(trace_frames[i]);
		address_str << i << ": 0x" << std::hex << address << "\n";
	}
	result.assign(address_str.str());
#else
	return std::string("get_backtrace_info error: unknow platform");
#endif
	return result;
}

void hogehoge() {
	std::cout << get_backtrace_info() << std::endl;
}

void hoge() {
	hogehoge();
}

int main() {
	hoge();
	return 0;
}
```

実行結果（Linux/OSX）

```text
./a.out(_Z8hogehogev+0x16) [0x8049526]
./a.out(_Z4hogev+0xb) [0x80495ab]
./a.out(main+0x12) [0x80495c2]
/lib/libc.so.6(__libc_start_main+0xe6) [0xa46d36]
./a.out() [0x8049111]
```

実行結果（Windows）

```text
1: 0x4017ff
2: 0x401855
3: 0x401867
4: 0x4013e2
5: 0x7d4e7d2a
```



実はこの情報を基づけばソースコードの行まで特定できます。
まずはコンパイルの時-gをつけること、そして以下のコマンドを実行して対応するソースコードを含めるアセンブリコードを出力します。
```objdump -C -S -M intel a.out > a.out.s```

.sファイルを取得したあとはこのコマンドでa.outに含まれているデバッグ情報（ソースコード）を消すことができます。
stripの結果はfileコマンドで確認できます。

```
$ strip a.out
$ file a.out
a.out ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
```


そのあとa.out.sをテキストエディタで開いて8049526を検索してみると以下の断片が見れます。

```text
	std::cout << get_backtrace_info() << std::endl;
 8049513:	56                   	push   esi
 8049514:	83 ec 34             	sub    esp,0x34
 8049517:	89 e0                	mov    eax,esp
 8049519:	8d 4d e8             	lea    ecx,[ebp-0x18]
 804951c:	89 08                	mov    DWORD PTR [eax],ecx
 804951e:	89 4d dc             	mov    DWORD PTR [ebp-0x24],ecx
 8049521:	e8 8a fc ff ff       	call   80491b0 <get_backtrace_info()>
 8049526:	83 ec 04             	sub    esp,0x4
```

さらに80495abを検索すると

```text
	hogehoge();
 80495a3:	83 ec 08             	sub    esp,0x8
 80495a6:	e8 65 ff ff ff       	call   8049510 <hogehoge()>
}
 80495ab:	83 c4 08             	add    esp,0x8
```
 
スタックを知ってる人なら既に分かると思います、[]の中の数値は関数の戻し先のアドレスになります。
これを調べるとget_backtrace_infoを呼び出す時に辿ったコードをすべて特定できます。

##エラーコードとその説明の取得
CとC++では多くの関数は失敗が発生した時にエラーコードをどこかに保存したあと失敗を示す返り値（-1など）を返します。
そのため返り値だけではエラーの原因を特定できません。
以下のコードはエラーコードとその説明を取得できます
上記と同じ動作環境にてテスト済み。

```cpp:last_error.cpp
#include <iostream>
#include <sstream>
#include <unistd.h>
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
	#define __posix__
	#include <execinfo.h>
#elif defined(__MINGW32__)
	#include <windows.h>
#endif

/// クロスプラットフォームエラー処理関数
#if defined(__posix__)
	int my_errno() { return errno; }
	void my_errno(int errnum) { errno = errnum; }
#elif defined(__MINGW32__)
	int my_errno() { return ::GetLastError(); }
	void my_errno(int errnum) { ::SetLastError(errnum); }
#endif // defined(__posix__)

/// エラーコードの説明文字列を取得します
std::string get_errno_explanation(int errnum) {
	const static size_t buffer_size = 1024;
	char buffer[buffer_size + 1] = {};
	const char* message = nullptr;
#if defined(__posix__) && defined(__linux__)
	message = strerror_r(errnum, buffer, buffer_size);
#elif defined(__posix__)
	message = (strerror_r(errnum, buffer, buffer_size) == 0) ? buffer : nullptr;
#elif defined(__MINGW32__)
	message = (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
		errnum, 0, buffer, buffer_size, nullptr) > 0) ? buffer : nullptr;
#else
	message = "get_errno_explanation error: unknow platform";
#endif
	std::string result;
	result.reserve(buffer_size + 20);
	if (message)
		result.append(message);
	result.append(" [errno ");
	result.append(std::to_string(errnum));
	result.append("]");
	return result;
}

int main() {
	if (::chdir("(´・ω・｀)") != 0) {
		int errnum = my_errno();
		std::cout << get_errno_explanation(errnum) << std::endl;
	}
	return 0;
}
```

実行結果（Linux/OSX）
```No such file or directory [errno 2]```

実行結果（Windows）

```
The system cannot find the path specified.
 [errno 3]
```

ちなみに、Windowsでもerrnoを使えますが一部の関数（例えばWSA...）はerrnoを設定してくれません、
そのためできればGetLastErrorを使うべきです。
またWSAGetLastErrorという関数もありますが、GetLastErrorとは同じ関数です。

##使われた関数のmanページ一覧
http://linux.die.net/man/3/backtrace
http://linux.die.net/man/3/backtrace_symbols
http://linux.die.net/man/3/errno
http://linux.die.net/man/3/strerror_r
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/intro.2.html
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strerror_r.3.html
(CaptureStackBackTrace) http://msdn.microsoft.com/en-us/library/windows/desktop/bb204633(v=vs.85).aspx
(GetLastError) http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360(v=vs.85).aspx
(SetLastError) http://msdn.microsoft.com/en-us/library/windows/desktop/ms680627(v=vs.85).aspx
(FormatMessage) http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx