デバッグ中に任意の例外を投げてみた

どもです。

テスト中に発生した例外時発生時の動作をデバッグで確認しようとすると、件の例外が中々あるいは全く発生しない場面ってあると思います。私も最近、そんな場面に遭遇しました。そんな場面、状況を打破するための方法を色々考え、自分なりの解を見つけました。今回は、この「自分なりの方法」について書きます。

1.はじめに

今回のエントリでは、以下の環境で作業を行っています。

単体テストの実行環境
項目 内容
OS Windows10 Pro(22H2)
CPU i7-8700
メモリ 16GB
IDE Visual Studio Commnuity 2022
version 17.3.6
ライブラリ MFC

2.自分なりの方法

自分なりに考え、見つけた自分なりの解は、

  1. 予め「特定の例外」をスロー、および関数を実装しておく。
  2. デバッグ中に、VisualStudioのイミディエイトウィンドウで、その関数を実行する。

という方法です。

なお、スローされた「特定の例外」をキャッチする処理は実装されている、という前提です。そのため、例外をキャッチする処理を追加で実装しません。

2.1.「特定の例外」をスローする関数

まず、「特定の例外」をスローする関数です。
これは、簡単♪

void throw_mfc_exception() {
	throw new CMemoryException();
}

ココでは、滅多に発生しない(と思われる)「メモリ不足例外」をスローするようにしています。

2.2.例外をキャッチする処理

これも簡単♪

int main()
{
	TRY
		LONG    argument1 = 10;
		LONG    argument2 = 23;
		LONG*	buffers = NULL;

		buffers = new LONG[2];

		buffers[0] = argument1;
		buffers[1] = argument2;

		LONG	result = buffers[0] + buffers[1];

		delete[] buffers;

		return (SHORT)result;
	CATCH(CMemoryException, ex)
		_tprintf(_T("A CMemoryException detected.\n"));

	AND_CATCH(CException, ex)
		_tprintf(_T("An exception detected.\n"));

	END_CATCH

	return 0;
}

テキトーが過ぎるにも程があるくらいテキトーに実装した関数です。
解説する価値もないので、コードは紹介するのみにしておきます。

2.3.イミディエイトウィンドウから関数を実行する

次は、イミディエイトウィンドウから例外を投げる関数を実行します。

イミディエイトウィンドウは、メニューの[デバッグ]-[ウィンドウ]-[イミディエイト]から表示できます。



デバッグ実行中にこのイミディエイトウィンドウから、れ外をスローする関数を実行します。実行方法は、以下の通り。

throw_mfc_exception()

このコードを実行することで、関数が実行、例外がスローされます。

3.やってみる

準備ができたので、実際にデバッグ中に例外をスローしてみます。
以下の手順を実施します。

  1. VisualStudio上、コードのテキトーな場所にブレークポイントを設定
  2. デバッグを開始
  3. ブレークポイントまで到達したら、イミディエイトウィンドウで、ココで紹介したコードを実行する。

実際の実行結果は、以下のようになります。



サンプルコードで紹介したコードのうち、CMemoryExceptionをキャッチした場合に実行されるコード」、メッセージが表示されています。

4.まとめ

今回は、デバッグ中に任意のタイミングで任意の例外を投げる方法について書きました。
この方法では、少なくとも「任意の場所で任意の例外を発生させる」ことは可能です。
しかし実際のところ、問題もあります。
それは、

例外をキャッチした処理でのブレークポイントが効かない

ということです。

今回のサンプルコードで言えば、CMemoryExceptionをキャッチしてメッセージを表示する処理に対してブレークポイントを設定しても、そこでは処理が停止しません。

加えて、例外をスローする関数と例外をスローしたい処理(今回のエントリでは、ブレークポイントを設定した処理)が同じバイナリである必要があります。そのため、例えばライブラリの任意のコードで例外を発生させたい場合には、その例外を発生させる関数、コードは同じライブラリに実装されている必要があります。デバッグ用、テスト用の関数を別途実装し、かつ製品版には含まれないようにするといった、管理の手間が増えてしまいます。

そういった理由で、今回実施してみた方法は勿論正解ではないし、最適解とも言えないと思います。

しかし、それでも、少なくとも最低限の目的は達成できているのではないかと思います。
このような方法でも、誰かの助けになれば幸いです。

ではっ!