パブリック シンボルとプライベート シンボル
他社でのダンプ解析やライブデバッグのために、自社ドライバのシンボルファイルを提供しないといけないけど、ローカル変数やソースコードの行番号まで出したくないと思ったことはありますか?
内部変数やソースコードの行番号がわかると、リバースエンジニアリングできてしまう可能性が高まったり、API を他社向けに提供している場合に、ローカル変数等がそれをご利用される他社様にわかり、それを前提にコーディングされてしまいますと、API を提供している側としては、その後の実装変更がやりづらくなったり、API を提供されている側としても互換性を保てなくなったりするリスクが高まるかと思います。かといって、それを恐れて、今困っているお客様の現象を調べるために、シンボルファイルを提供しない、というのも、問題の解決が遅れたり、誤った解析結果につながり、お客様の信頼を失うという別のリスクにつながりかねません。
今回は、そのような皆様に、パブリック シンボルとプライベート シンボルのご紹介をしたいと思います。誤解を恐れずにイメージだけを端的に言えば、ローカル変数やソースコードの行番号が含まれる方が「プライベート シンボル」、それらを除いた関数名やグローバル変数が含まれる方が「パブリック シンボル」です。今回の記事では、シンボルについておさらいをした後、パブリックシンボルとプライベートシンボルの違いについてご説明し、最後にその両方をどのように作成するかをご説明します。
-
- シンボルとシンボル ファイル
まず、シンボルとシンボル ファイルについて、おさらいしておきましょう。
シンボル ファイルとは、ドライバ (.sys や.dll) やアプリケーション (.exe) をビルドすると、一緒に作成される、同じ名前の .pdb という拡張子を持つファイルです。
通常、シンボル ファイルは以下のものを含みます。
・グローバル変数
・ローカル変数
・関数名とそのエントリポイントのアドレス
・FPO (Frame Pointer Omission) レコード
・ソースコードの行番号
厳密には、これらのそれぞれを、個別に「シンボル」と呼びます。
ただ、私たちがお客様に「シンボルをください」とお願いする時は、シンボル
ファイルそのものを指すことが多いです。
シンボル ファイルが必要な理由は、効率性の面で様々ありますが、それよりも正確性の面で、今見ているコールスタックが間違っていない状態にする必要があるからです。例えば「WARNING: Stack unwind information not available. Following frames may be wrong.」とデバッガに表示される場合、表示されているコールスタックのうち、この行より下にあるフレームはデバッガが推測したものとなり、この推測が間違っていることも多いです。間違った情報を基に解析しても、正しい結論にたどり着くのは困難であるため、私たちサポートは、開発者のお客様にシンボルファイルの提供をお願いしています。
-
- パブリック シンボルとプライベート シンボル
続いて、パブリック シンボルとプライベート シンボルについてです。
少しややこしいかもしれませんが、まず、「プライベート シンボル データ」と「パブリック シンボル テーブル」について説明し、その後、「プライベート シンボル ファイル」と「パブリック シンボル ファイル」について説明します。
プライベート シンボル データ (private symbol data) は、主に以下を含みます。
・関数
・グローバル変数
・ローカル変数
・ユーザーが定義した構造体、クラス、データ型
・ソースファイル名や、バイナリの各命令に対応するソースの行番号
・FPO レコード
パブリック シンボル (public symbol table) は、主に以下を含みます。
・関数(static で宣言されたもの以外)
・extern として指定されたグローバル変数
・FPO レコード
プライベート シンボル ファイルは、別名 Full Symbol Files と言われる通り、プライベート シンボル データとパブリック シンボル テーブルの両方を含むシンボル ファイルです。パブリック シンボル ファイルは、別名Stripped Symbol Files と言われる通り、パブリック シンボル テーブル(もしくはその一部) のみを含みます。
本ブログエントリの冒頭で、”ローカル変数やソースコードの行番号が含まれる方が「プライベート シンボル」、それらを除いた関数名やグローバル変数が含まれる方が「パブリック シンボル」 という言い方をしました。これは、「プライベート シンボル ファイル」と「パブリック シンボル ファイル」のことを言っています。これ以降も、「プライベート シンボル」と「パブリック シンボル」と言えば、それぞれファイルのことを言っているとご理解いただければと思います。
なお、同じ冒頭で、プライベート シンボルを公開するリスクについてお話しましたが、弊社にサポートをご依頼いただく場合は、プライベート シンボルをご提供いただいた方がよい場合が多いと思います。その理由は、お客様自身に解析結果をご提供する際、お客様自身がすぐにご自身のソースコードのどこ(ローカル変数やソースの行番号)に関連するのかがわかりやすくなるためです。お客様のご都合に応じて、適切と思われる方をお選びいただければ幸いです。
-
- 作り方
最後に、パブリック シンボルとプライベート シンボルの作り方をご説明します。
と言っても、通常は、Visual Studio 等でビルドするだけで、プライベート シンボルが作られます。
そして、この時、「/PDBSTRIPPED (プライベート シンボルの除去)」のリンカー オプションを設定しておくことで、プライベート シンボルと同時にパブリックシンボルを作ることができます。
具体的には、以下の手順を実施します。
(1) Visual Studio 2015 で該当ドライバのソリューション(.sln) を開きます。
(2) ソリューション
エクスプローラーで、プロジェクトを右クリックして、 [プロパティ] をクリックします。
(3) 左側のペインで、 [構成プロパティ]-[リンカー]-[デバッグ] を開き、右側の [プライベート シンボルの削除] に、「$(TargetName).pdb」と記述します。ファイル名を、パブリック シンボルとプライベート シンボルで同じファイル名となるようにし、出力フォルダのパスのみ別にするためです。該当プロジェクト(.vcxproj) と同じフォルダにパブリックシンボルが出力されます。
以下は、netvmini サンプルのnetvmini\6x フォルダのnetvmini.sln ソリューションを使用して、netvmini630 プロジェクト(630 フォルダのnetvmini630.vcxproj) を使用した場合のプロパティの例です。
ご注意
「$(OutDir)Public\$(TargetName).pdb」のように、上記以外のパスに出力されるようにしたい場合、$(OutDir) のフォルダ(例えばRelease) の下に、予めPublic というフォルダを作成しておく必要があります。これをしないと、「LINK : fatal error LNK1201: プログラム データベース'D:\develop\blog\PDBSTRIPPED\netvmini\6x\630\Release\Public\netvmini630.pdb' に書き込み中にエラーが発生しました。ディスク容量の不足、無効なパス、または特権の不足が原因でないことを確認してください。」のようなリンクエラーとなり、ビルドに失敗します。この場合も、ファイル名は、パブリック シンボルとプライベート シンボルで同じファイル名となるように「$(TargetName).pdb」としておき、出力フォルダのパスのみ別にします。
(4) ウィンドウ右下の [適用] または [OK] をクリックします。
(5) ソリューション、またはプロジェクトをビルドします。
上述の画面例では、netvmini\6x\630 フォルダにパブリック シンボル、netvmini\6x\630\Release フォルダにプライベート シンボルが作成されます。
補足:コマンドツールについて
コマンドで上記と同様の処理を行いたい場合、binplace.exe とpdbcopy.exe の 2 つがご利用いただけます。いずれも、プライベート シンボルからパブリック シンボルを作成するのに使います。
BinPlace ツールは、WDK 10 をインストールすると、\Program Files (x86)\Windows Kits\10\bin\x86 にbinplace.exe としてインストールされます。ただ、binplace.exe は、2017 年 9 月現在、適切に機能しないことを確認しております。現在、詳細を調査中ですが、上記の「/PDBSTRIPPED (プライベート シンボルの除去)」のリンカー オプションをご利用いただくか、以下のPDBCopy ツールをご利用いただくことで回避できます。
PDBCopy ツールは、\Program Files (x86)\Windows Kits\10\Debuggers\<arch> にpdbcopy.exe としてインストールされます。(<arch> はx86 やx64 です。)
これを、以下のように実行します。
pdbcopy.exe < プライベート シンボルへの相対パス > < パブリック シンボルへの相対パス > -p
「3. 作り方」で上述した、パブリック シンボルとプライベート シンボルを同時に生成する手順の例の、プライベート シンボルに、PDBCopy ツールを使用した例をお見せしたいと思います。
コマンドプロンプトを起動し、netvmini\6x\630 に cd コマンドで移動して、以下を実行します。
> "c:\Program Files (x86)\Windows Kits\10\Debuggers\x64\pdbcopy.exe" Release\netvmini630.pdb Release\Public\netvmini630.pdb -p |
なお、Release フォルダの下に予めPublic フォルダを作成した状態で実行しています。作成していないと、「Error: EC_FILE_SYSTEM -- Release\Public\netvmini630.pdb」というエラーが表示されます。
補足2:シンボル ファイルがパブリック シンボルかプライベート シンボルかを確認する方法
シンボル ファイルがパブリック シンボルかプライベート シンボルかを確認するには、Symchk ツールを使います。
Symchk ツールは、WDK 10 をインストールすると、C:\Program Files (x86)\Windows Kits\10\Debuggers\<arch>\ フォルダにあります。(<arch> はx64 や x86 です。)
これを、以下のように実行します。
Symchk.exe /v <.sys ファイルへの相対パス > /s < シンボル ファイルが存在するフォルダへの相対パス > /ps
/v はverbose つまり詳細なログ出力をするためのオプションです。/ps は、.pdb ファイルからソースの行番号やデータ型などが strip されたかどうかをチェックするためのオプションです。
「3. 作り方」で上述した、パブリック シンボルとプライベート シンボルを同時に生成する手順の例のそれぞれのシンボル ファイルに、Symchk ツールを使用した例をお見せしたいと思います。
コマンドプロンプトを起動し、netvmini\6x\630 に cd コマンドで移動して、以下を実行します。
<パブリック シンボルの場合>
> "c:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symchk.exe" /v Release\netvmini630.sys /s .\ /ps(略)DBGHELP: netvmini630 - public symbols .\netvmini630.pdb(略)SYMCHK: FAILED files = 0SYMCHK: PASSED + IGNORED files = 1 |
<プライベート シンボルの場合>
> "c:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symchk.exe" /v Release\netvmini630.sys /s Release\ /ps(略)DBGHELP: netvmini630 - private symbols & lines Release\netvmini630.pdb(略)SYMCHK: netvmini630.sys FAILED - netvmini630.pdb is not stripped.
SYMCHK: FAILED files = 1 SYMCHK: PASSED + IGNORED files = 0 |
上記の黄色文字で記述した部分で、それぞれパブリック シンボルであるか、プライベート シンボルであるかをご確認いただけます。
参考文献
Symbols and Symbol Files
/en-us/windows-hardware/drivers/debugger/symbols-and-symbol-files
Public and Private Symbols
/en-us/windows-hardware/drivers/debugger/public-and-private-symbols
/PDBSTRIPPED (Strip Private Symbols)
/en-us/cpp/build/reference/pdbstripped-strip-private-symbols
/PDBSTRIPPED (プライベート シンボルの除去)
https://msdn.microsoft.com/ja-jp/library/y87kw2fd.aspx
BinPlace
/en-us/windows-hardware/drivers/devtest/binplace
PDBCopy
/en-us/windows-hardware/drivers/debugger/pdbcopy
Using SymChk
/en-us/windows-hardware/drivers/debugger/using-symchk
以上の内容がお役に立ちましたら幸いです。
WDK サポートチーム 津田