前文
CおよびC ++プログラムは、ほとんどの人生を
main()
関数内で費やし、
main()
から直接または間接的に呼び出される関数を費やす傾向があります。 ただし、実際には、プログラムの実行は
main()
で始まるのではなく、コンパイラに付属する標準ライブラリのコードで始まります。 そのようなコードは、理論的には、
main()
によって呼び出される可能性のある標準ライブラリの他の関数の環境、および
main()
パラメーターを準備する
argc/argv/envp
があります(Windows; Unixシステムは、準備された形式で
argc/argv/envp
を渡す傾向があります
main()
プロセスを開始するときですが、それはそれらについてではありません)。 対称的に、
main()
関数の最後の
return
は、プログラムの最後の命令ではありません。標準ライブラリからもう少しコードが続きます。
Visual Studioでは、プログラムへの「実際の」エントリポイントは
mainCRTStartup
と呼ばれ
mainCRTStartup
。 標準ライブラリのソースコードはVSに付属しています。VS2015では、mainCRTStartupの定義は
%PROGRAMFILES(X86)%\VC\crt\src\vcruntime\exe_main.cpp
にありますが、ちなみに、
exe_common.inl
は近くのすべての作業を行います。 そこを見てみましょう。
... // If this module has any thread-local destructors, register the // callback function with the Unified CRT to run on exit. _tls_callback_type const * const tls_dtor_callback = __scrt_get_dyn_tls_dtor_callback(); if (*tls_dtor_callback != nullptr && __scrt_is_nonwritable_in_current_image(tls_dtor_callback)) { _register_thread_local_exe_atexit_callback(*tls_dtor_callback); } __telemetry_main_invoke_trigger(nullptr); // // Initialization is complete; invoke main... // int const main_result = invoke_main(); // // main has returned; exit somehow... // __telemetry_main_return_trigger(nullptr); if (!__scrt_is_managed_app()) exit(main_result); if (!has_cctor) _cexit(); // Finally, we terminate the CRT: __scrt_uninitialize_crt(true, false); return main_result; ...
もっと深く
経験豊富な妄想家は、間違いなく
__telemetry_main_invoke_trigger
と
__telemetry_main_return_trigger
課題にすでに気付いています。 それらのソースを見つけてみましょう...と置物。 これらの関数内に移動しようとすると、VSデバッガーは「telemetry.cpp not found」(MSが配信に含めるのを忘れたソースファイルはtelemetry.cppと呼ばれることを意味します。論理的に)を報告し、パスを手動で指定するか、逆アセンブルされたコード。
__vcrt_initialize_telemetry_provider
から他の関数を注意深く検索すると、初期化および完了時にそれぞれ呼び出される関数
__vcrt_initialize_telemetry_provider
および
__vcrt_uninitialize_telemetry_provider
さらにいくつか見つかります。
もちろん、ソースを提供できなかったからといって、内部を見ることはできません。 逆アセンブルされたコードを調べると、タイプ
const __vcrt_trace_logging_provider::_TlgProvider_t* const
の_Microsoft_CRTProvider変数につながり、タイプ
_TlgProvider_t
もはや秘密で
_TlgProvider_t
なく、SDKに簡単に配置されます:
%PROGRAMFILES(X86)%\Windows Kits\10\Include\10.0.10586.0\shared\TraceLoggingProvider.h
... そして、ここにドキュメントがあります 。 (ドキュメントには「Windows 10」と書かれていますが、これはコードがWindows 7で動作することを妨げるものではありません。)では、これらすべてのログをどこに書き込むのでしょうか。
このセクションで説明するように、TraceLoggingイベントはETWに送信されます。つまり、これはWindowsサブシステムのイベントトレースの別の化身です。 うん。
ETWという略語を最初に聞いた人への簡単なリファレンス:これは、Windows 2000で登場し、Vistaで大幅に増加した、あらゆる種類のログとカウンタの統合処理のためのインフラストラクチャです。 希望する場合は、コマンドプロンプトで
logman query providers
を入力して、スケールを評価できます。
ログを見る
例えば、いくつかの簡単なプログラムを見てみましょう:
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
cl /Os hello.c
によるコンパイル結果: yadi.sk/d/pa0S5qVoqw9Q4
そのため、コンパイルされたexe-shnikには、
main()
呼び出す前後のいくつかのログの記録があるはずです。 ETWサブシステムは、ロギング用のコマンドがなかったものをすべて破棄します。 ログをオンにしましょう:管理者名、
logman create trace test_crt_telemetry -p {5EEC90AB-C022-44B2-A5DD-FD716A222A15} -o C:\temp\test_telemetry logman start test_crt_telemetry
(
logman
必要な
logman
と
tracerpt
は、Windowsの標準ユーティリティです)。 {5EEC90AB-C022-44B2-A5DD-FD716A222A15}はどこで入手しましたか? 既に説明した変数
_Microsoft_CRTProvider
表示すると、VSデバッガーが表示され
_Microsoft_CRTProvider
。
hello.exeを起動すると、古典的な挨拶が表示されます。 ログをファイルにリセットし、
logman stop test_crt_telemetry
そしてそこに書かれているものを見てください:
tracerpt -summary summary.txt -o dumpfile.xml C:\temp\test_telemetry_000001.etl
<Events> <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft.CRTProvider" Guid="{5eec90ab-c022-44b2-a5dd-fd716a222a15}" /> <EventID>17</EventID> <Version>0</Version> <Level>5</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x200000000000</Keywords> <TimeCreated SystemTime="2016-04-11T00:57:29.437589800Z" /> <Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" /> <Execution ProcessID="7656" ThreadID="5796" ProcessorID="0" KernelTime="0" UserTime="0" /> <Channel /> <Computer /> </System> <EventData> <Data Name=""Main Invoked."">Main Invoked.</Data> <Data Name="FileName">C:\temp\hello.exe</Data> </EventData> <RenderingInfo Culture="ru-RU"> <Task>InvokeMainViaCRT</Task> </RenderingInfo> </Event> <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft.CRTProvider" Guid="{5eec90ab-c022-44b2-a5dd-fd716a222a15}" /> <EventID>77</EventID> <Version>0</Version> <Level>5</Level> <Task>0</Task> <Opcode>0</Opcode> <Keywords>0x200000000000</Keywords> <TimeCreated SystemTime="2016-04-11T00:57:29.437734300Z" /> <Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" /> <Execution ProcessID="7656" ThreadID="5796" ProcessorID="0" KernelTime="0" UserTime="0" /> <Channel /> <Computer /> </System> <EventData> <Data Name=""Main Returned."">Main Returned.</Data> <Data Name="FileName">C:\temp\hello.exe</Data> </EventData> <RenderingInfo Culture="ru-RU"> <Task>ExitMainViaCRT</Task> </RenderingInfo> </Event>
はい、ログがあります。 ただし、多くのデータはありません。main
main()
および標準ETWヘッダーの呼び出し/戻りに関するメッセージに加えて、exe-shnikの名前のみが書き込まれます。
ちなみに、ログエントリをオンのままにして作業した場合はどうなりますか? たとえば、キャッチできます
<EventData> <Data Name=""Main Invoked."">Main Invoked.</Data> <Data Name="FileName">C:\Program Files\Python 3.5\python35.dll</Data> </EventData> <EventData> <Data Name=""Main Invoked."">Main Invoked.</Data> <Data Name="FileName">C:\Program Files\Python 3.5\python.exe</Data> </EventData> <EventData> <Data Name=""Main Returned."">Main Returned.</Data> <Data Name="FileName">C:\Program Files\Python 3.5\python.exe</Data> </EventData> <EventData> <Data Name=""Main Returned."">Main Returned.</Data> <Data Name="FileName">C:\Program Files\Python 3.5\python35.dll</Data> </EventData>
パニックにならないで
最後に何がありますか?
- CまたはC ++プログラムからVS2015によってコンパイルされたバイナリには、ログを書き込むことができるコードがあります。 (退屈モード:標準ライブラリを無効にするために特別な努力を払わない場合。ただし、それを使用せずにCで記述できる場合は、C ++で-最初の例外までのみ)
- ログは乏しく、システムがそれなしでは知らなかったということは特に興味深いものはありません。 「
main()
名目上コントロールを返す」ケースと「exit
またはabort
と呼ばれるケース」を区別することは可能ですが、これは開発者がデバッグするのにかなり興味深いものです。 妄想的な人はリラックスできます。 - しかし、前例自体は興味深いものです。
- デフォルトでは、ログはどこにも書き込まれません。 具体的に含める必要があります。 ただし、包含コマンドは、プログラムまたはサブジェクト領域についてまったく何も知らない場合があります(logmanもtracerptも特定の
Microsoft.CRTProvider
認識していません-上記のログの構造全体がログと共に書き込まれます)。 - 次のコードを使用して、ログが有効になっているかどうかを確認できます。
#include <Windows.h> #include <evntprov.h> static void NTAPI EnableCallback(LPCGUID, ULONG isEnabled, UCHAR, ULONGLONG, ULONGLONG, PEVENT_FILTER_DESCRIPTOR, PVOID context) { *(bool*)context = (bool)isEnabled; } typedef ULONG (WINAPI *EventSetInformation_t)(REGHANDLE, EVENT_INFO_CLASS, PVOID, ULONG); #pragma pack(push, 1) static struct { unsigned short TotalSize; char ProviderName[22]; unsigned short Chunk1Size; unsigned char Chunk1Type; GUID GroupGuid; } _Microsoft_CRTProvider_traits = { 0x2B, "Microsoft.CRTProvider", 0x13, 1, { 0x4F50731A, 0x89CF, 0x4782, 0xB3, 0xE0, 0xDC, 0xE8, 0xC9, 0x04, 0x76, 0xBA }, }; static_assert(sizeof(_Microsoft_CRTProvider_traits) == 0x2B, "invalid size"); #pragma pack(pop) int main() { static const GUID providerId = { 0x5eec90ab, 0xc022, 0x44b2, 0xa5, 0xdd, 0xfd, 0x71, 0x6a, 0x22, 0x2a, 0x15 }; REGHANDLE hProvider; bool enabled = false; ULONG status = EventRegister(&providerId, &EnableCallback, &enabled, &hProvider); if (status == ERROR_SUCCESS) { EventSetInformation_t EventSetInformation = (EventSetInformation_t)GetProcAddress(GetModuleHandleA("advapi32.dll"), "EventSetInformation"); if (EventSetInformation) EventSetInformation(hProvider, EventProviderSetTraits, &_Microsoft_CRTProvider_traits, sizeof(_Microsoft_CRTProvider_traits)); EventUnregister(hProvider); } printf("Microsoft.CRTProvider logging is %s\n", enabled ? "on" : "off"); return 0; }
- このようなログをデバッグ支援として使用するのが最も論理的です。 ただし、デバッガ自体の下での起動にはログは含まれません。 もちろん、設定の一部のチェックボックスにログを必要とする機能が含まれている可能性があります...またはログがトリッキーなデバッグユーティリティに必要です...しかし、リリースライブラリでコンパイルするときにコードが残る理由は明らかではありません。
2016年4月13日12:00からのUPD:ログのアクティブユーザーの存在を検出するためのコードを更新しました。これは、__ vcrt_initialize_telemetry_provider内で起こっていることに正確に対応します。