重箱の隅のデバッグ(2) - エラーの意味を探る

皆さんこんちは。
Platform SDK チームの Chiharun です。

「重箱の隅のデバッグ」シリーズ第 2 回は API から返されたエラーコードの定義を調べる方法についてご紹介します。

プログラムコードの中で API を呼び出して、API の戻り値をチェックすることによって API が正常に完了したか確認することができます。 SDK や DDK/WDK を使ったことのある方は以下のようなヘッダファイルをソースファイルに直接的あるいは間接的にインクルードしてヘッダファイルで定義されているエラーコードの定義名を参照したことがあることでしょう。

  • winerror.h
  • ntstatus.h

プログラムを WinDbg でデバッグする場合にも API の返すエラーの値を調べることは良くあります。 例えばある値が API の戻り値として返された場合に、その値を上記のヘッダファイルに対して文字列検索で調べるといった場合があります。

私も以前はヘッダファイルを検索してエラー値の定義名を求めていました。 現在は別の方法でエラーに該当するヘッダファイル側の定義名を求めています。 理由としてはマイクロソフトの提供するすべての製品で利用されている API の戻り値を上記の 2 個のヘッダファイルで網羅していないため、検索してもヒットしない場合があるからです。

Err.exe - エラーコードを見つけるツール
今回ご紹介するツールは err.exe と呼ばれるコンソールプログラムです。 マイクロソフトのダウンロードセンターから入手することができます。

Microsoft Exchange Server Error Code Look-up
https://www.microsoft.com/download/en/details.aspx?id=985

ダウンロードセンターからダウンロードした自己解凍プログラム Err.EXE を実行すると、以下のファイルを展開することができます。 あとは Err.exe に Path を通せばコマンドコンソールから利用する準備が整います。

  • 2004/04/01 18:18 1,698,816 Err.exe
  • 2004/04/01 18:26 505,344 Error Code Lookup Tool.doc
  • 2004/04/01 17:43 13,372 eula.txt

(補足 Path の通し方について)
コマンドコンソールでコマンドを入力すると環境変数の Path で定義されているディレクトリ内に存在する実行可能ファイルを検索し、入力したコマンドに該当する名前のプログラムを実行します。以下の方法のどちらかで Err.exe に Path を通すことができます。

方法 1: 環境変数の Path に定義されているディレクトリに Err.exe をコピーします。環境変数の Path に設定されている値を確認する場合は以下のコマンドで確認します。

set path

方法 2: Err.exe を展開したディレクトリを環境変数 Path に追加する。たとえば Err.exe を展開したディレクトリが C:\ErrTools とした場合、次のコマンドで Path を通すことができます。

set path=C:\ErrTools;%path%

基本的な使い方
以下のように err の後ろに API から返された値を指定すると、該当するヘッダファイルの名前と定義された定義名を出力します。 この例では 3e5 を指定しました。コマンドコンソールで入力した文字は緑色の大きめの太字で示します。

err 3e5
# for hex 0x3e5 / decimal 997 :
  ERROR_IO_PENDING winerror.h
# Overlapped I/O operation is in progress.
# 1 matches found for "3e5"

API から戻された値 3e5 は winerror.h で ERROR_IO_PENDING として定義されていることがわかります。

指定する値は 16 進数である必要はありません。 0x3e5 は 10 進数の 997 ですので 997 を指定して err.exe を実行してみましょう。

err 997
# for decimal 997 / hex 0x3e5 :
  ERROR_IO_PENDING winerror.h
# Overlapped I/O operation is in progress.
# for hex 0x997 / decimal 2455 :
  NERR_NetlogonNotStarted lmerr.h
# 2 matches found for "997"

err.exe が 10 進数の 997 に該当するのは ERROR_IO_PENDING であることと、16 進数の 0x997 に該当するのは lmerr.h で定義されている NERR_NetlogonNotStarted であることを示しています。このように err.exe で指定するエラー値は 10 進数あるいは 16 進数を意識しなくても err.exe 側で該当する情報をすべて表示します。

もちろん指定した値が 16 進数であることを明示的に示すために、指定する数値の前に 0x を指定して実行することもできます。以下の例では 16 進数の 997 であることを示すために 0x997 として指定した場合の実行例です。

err 0x997
# for hex 0x997 / decimal 2455 :
  NERR_NetlogonNotStarted lmerr.h
# 1 matches found for "0x997"

上記の例では 997 は明示的に 16 進数として 0x997 を指定しましたので、10 進数の 997 は検索されず、lmerr.h で定義されている NERR_NetlogonNotStarted のみが検索されました。

応用的な使い方
err.exe は文字列も指定することができます。 完全文字列検索の場合は等号記号(=)を、部分文字列検索の場合はコロン(:)を文字列の前に指定します。 ワイルドカードはありません。 特定のヘッダファイルで設定されているエラー値のみを検索することもできます。

完全文字列検索
指定する文字列と完全に一致する定義がヘッダファイルにあることがわかっている場合はイコール記号(=)を指定します。

err =STATUS_PENDING
# matching string "STATUS_PENDING" :
0x00000103 STATUS_PENDING ntstatus.h
# The operation that was requested is pending completion.
# 1 matches found for "=STATUS_PENDING"

上記の例ではヘッダファイルで定義されている STATUS_PENDING は ntstatus.h に存在しており 0x103 として定義されていること表示します。

部分文字列検索
以下は部分文字列として peinding を含むヘッダファイルの定義を検索した例です。 検索対象の部分文字列の前方にコロン(:)を指定します。

err :pending
# matching string "pending" :
DB_E_PENDINGCHANGES allerror.h
DB_E_PENDINGINSERT allerror.h
ARAPERR_PENDING arapio.h
KERNEL_APC_PENDING_DURING_EXIT bugcodes.h
<途中省略>
ERROR_INTERNET_DIALOG_PENDING wininet.h
AT_TEST_PENDING_EXPIRATION wlevents2.mc
# The certificate's issuer is no longer allowed by the
# autoenrollment object.
# 63 matches found for ":pending"

実行結果の一部を省略しておりますが pending を含む定義はヘッダファイル内に 63 個見つかりました。

パイプを使って findstr と組み合わせる
文字列検索ではワイルドカードの指定はできません。 しかしながらパイプ(|)を用いて err.exe の出力結果からさらに特定の文字列検索をおこなうことができます。 以下の例では pending  を含むエラーの定義の中から _IO_ を含むものを検索した例です。

err :pending | findstr _IO_
  ERROR_IO_PENDING winerror.h

ヘッダファイルの指定
特定のヘッダファイルで定義されている戻り値を検索する場合はスラッシュ(/) を用いてヘッダファイル名を明示的に指定します。

err /winerror.h /ntstatus.h 0
# winerror.h selected.
# ntstatus.h selected.
# for hex 0x0 / decimal 0 :
  STATUS_WAIT_0 ntstatus.h
  ERROR_SUCCESS winerror.h
# The operation completed successfully.
  NO_ERROR winerror.h
  SEC_E_OK winerror.h
  S_OK winerror.h
# 5 matches found for "0"

上記の例では winerror.h と ntstatus.h に含まれる 0 として設定されている戻り値が出力されました。

制約事項
err.exe は非常に便利なツールですが万能ではありません。err.exe は 2003 年 3 月 31 日に作成されたツールです。 その後にヘッダファイルに追加された値を検索することはできません。 たとえば最新の WDK (Windows Driver Kit) の ntstatus.h で定義されている以下の  STATUS_VHD_FORMAT_UNKNOWN の定義は err.exe で検索することができません。

#define STATUS_VHD_FORMAT_UNKNOWN ((NTSTATUS)0xC03A0004L)

試しに上記のエラーの文字列 STATUS_VHD_FORMAT_UNKNOWN を検索してみましょう。

err =STATUS_VHD_FORMAT_UNKNOWN
# NOT FOUND: =STATUS_VHD_FORMAT_UNKNOWN

VHD (Virthual Hard Disk) のエラーコードは Windows Server 2008 の SDK から ntstatus.h に含まれました。 err.exe が作成された 2003 年時点での ntstatus.h で定義されていない値については検索することができません。

まとめ
err.exe には 172 個のヘッダファイルに含まれる 19871 個の戻り値の定義が含まれております。 172個のヘッダファイルを常に手元に置き、API の戻り値を定義するヘッダファイルを検索するのは大変な労力であるばかりではなく、検索する値を 10 進数、および 16 進数で検索する必要があります。 またヘッダファイル側の定義が別の定義名を参照していた場合は、数値を文字列検索してもヒットしません。 err.exe は地味なツールですが必要なことを着実にこなすツールです。 私の PC には err.exe を常にインストールしてあります。

あとがき
今回は「重箱の隅のデバッグ」第 2 回として API から返された値のヘッダファイル側での定義を検索するツールを紹介しました。 次回はデバッグをおこなう時にエラーの出所を調べる方法について説明します。

------------------------------------------
「重箱の隅のデバッグ」シリーズ
重箱の隅のデバッグ(1) - インポートセクションで設定するブレークポイント
https://blogs.msdn.com/b/japan_platform_sdkwindows_sdk_support_team_blog/archive/2012/04/09/10290167.aspx