概要
今日はスタックの読み方について考察します。スタックを分析した時に、”+0x” というのが右端に表示されるのですが、これは一体何を表しているのか、ソースコードの行数と比較して考察してみたいと思います。 レジスタの細かい話等は一旦置いておきこの投稿では感覚を掴みたいと思います。”Windows ダンプの極意” という書籍も参考にしてます。
Key Takeaways
- 検証および書籍等から、”+0x” というのは当該関数の先頭からの相対アドレス (オフセット) と思われる。
- ソースコードの行数とは全く異なる。
詳細
簡易プログラムで検証してみる
まず以下のような簡易的なプログラムを作成しました。Sleep します。
using System.Diagnostics;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
int result = Calculate(7, 3);
Console.WriteLine("Log Info : final result = {0}", result);
Console.WriteLine("Sayonara, World!");
}
static int Calculate(int a, int b) {
int res = Plus(a, b);
Console.WriteLine("Log Info in Calculate method: res = {0}",res);
//Debug.WriteLine("Log Info : a = {0}, b = {1}", a, b);
return res * res;
}
static int Plus(int a, int b)
{
Console.WriteLine("Log Info in Plus method: a = {0}, b = {1}", a, b);
//Debug.WriteLine("Log Info : a = {0}, b = {1}", a, b);
Console.WriteLine("Sleep START");
Thread.Sleep(60000);
Console.WriteLine("Sleep END");
return a + b;
}
}
}
Visual Studio でビルドしてできあがった成果物 (今回は Debug ビルドでやっています) が以下にあるので、実行ファイルである exe を直接実行します。
Sleep で wait するところで、タスク マネージャーから当該プロセスを見つけて右クリックし「メモリ ダンプ ファイルの作成」を押下して、DMP ファイルを出力します。
DMP ファイルを WinDbg で開きます。とりあえず、全スレッドのスタックをみてみましょう。0番スレッドが当該処理のスレッドのようですね。
0:000> ~*kL
. 0 Id: 51a0.e9c Suspend: 0 Teb: 00000052`d54ea000 Unfrozen
# Child-SP RetAddr Call Site
00 00000052`d577e758 00007ffd`dab851b3 ntdll!NtDelayExecution+0x14
01 00000052`d577e760 00007ffd`d7eb54fd ntdll!RtlDelayExecution+0x43
02 00000052`d577e790 00007ffc`d1a2b022 KERNELBASE!SleepEx+0x7d
03 00000052`d577e810 00007ffc`d1a2aefb coreclr!Thread::UserSleep+0xd2
04 00000052`d577e870 00007ffc`cf28e751 coreclr!ThreadNative::Sleep+0x9b
05 00000052`d577e9c0 00007ffc`71f11b1e System_Private_CoreLib!System.Threading.Thread.Sleep+0x11
06 00000052`d577e9f0 00007ffc`71f11a1a ConsoleApp2!ConsoleApp2.Program.Plus+0x9e
07 00000052`d577ea40 00007ffc`71f11977 ConsoleApp2!ConsoleApp2.Program.Calculate+0x3a
08 00000052`d577ea90 00007ffc`d1a9a333 ConsoleApp2!ConsoleApp2.Program.Main+0x47
09 00000052`d577ead0 00007ffc`d19c7c39 coreclr!CallDescrWorkerInternal+0x83
0a (Inline Function) --------`-------- coreclr!CallDescrWorkerWithHandler+0x56
0b 00000052`d577eb10 00007ffc`d1a87ea8 coreclr!MethodDescCallSite::CallTargetWorker+0x2a1
0c (Inline Function) --------`-------- coreclr!MethodDescCallSite::Call+0xb
0d 00000052`d577ec50 00007ffc`d1a25f4e coreclr!RunMainInternal+0x11c
0e 00000052`d577ed70 00007ffc`d1a26283 coreclr!RunMain+0xd2
0f 00000052`d577ee20 00007ffc`d1a25471 coreclr!Assembly::ExecuteMainMethod+0x1bf
10 00000052`d577f0f0 00007ffc`d1a41a08 coreclr!CorHost2::ExecuteAssembly+0x281
11 00000052`d577f260 00007ffd`6bf429d6 coreclr!coreclr_execute_assembly+0xd8
12 (Inline Function) --------`-------- hostpolicy!coreclr_t::execute_assembly+0x2a
13 00000052`d577f300 00007ffd`6bf42cbc hostpolicy!run_app_for_context+0x596
14 00000052`d577f490 00007ffd`6bf435fa hostpolicy!run_app+0x3c
15 00000052`d577f4d0 00007ffd`9c7bb5c9 hostpolicy!corehost_main+0x15a
16 00000052`d577f5d0 00007ffd`9c7be006 hostfxr!execute_app+0x2e9
17 00000052`d577f6d0 00007ffd`9c7c028c hostfxr!`anonymous namespace'::read_config_and_execute+0xa6
18 00000052`d577f7c0 00007ffd`9c7be5e4 hostfxr!fx_muxer_t::handle_exec_host_command+0x16c
19 00000052`d577f870 00007ffd`9c7b85a0 hostfxr!fx_muxer_t::execute+0x494
1a 00000052`d577f9b0 00007ff6`0d2df9d8 hostfxr!hostfxr_main_startupinfo+0xa0
1b 00000052`d577fab0 00007ff6`0d2dfde6 ConsoleApp2_exe!exe_start+0x878
1c 00000052`d577fc80 00007ff6`0d2e1328 ConsoleApp2_exe!wmain+0x146
1d (Inline Function) --------`-------- ConsoleApp2_exe!invoke_main+0x22
1e 00000052`d577fcf0 00007ffd`d966257d ConsoleApp2_exe!__scrt_common_main_seh+0x10c
1f 00000052`d577fd30 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
20 00000052`d577fd60 00000000`00000000 ntdll!RtlUserThreadStart+0x28
1 Id: 51a0.1518 Suspend: 0 Teb: 00000052`d54f0000 Unfrozen
# Child-SP RetAddr Call Site
00 00000052`d5bff6f8 00007ffd`dab6537e ntdll!NtWaitForWorkViaWorkerFactory+0x14
01 00000052`d5bff700 00007ffd`d966257d ntdll!TppWorkerThread+0x2ee
02 00000052`d5bff9e0 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
03 00000052`d5bffa10 00000000`00000000 ntdll!RtlUserThreadStart+0x28
2 Id: 51a0.3a64 Suspend: 0 Teb: 00000052`d54f2000 Unfrozen ".NET EventPipe"
# Child-SP RetAddr Call Site
00 00000052`d5d7f5e8 00007ffd`d7ed0479 ntdll!NtWaitForMultipleObjects+0x14
01 00000052`d5d7f5f0 00007ffd`d7ed037e KERNELBASE!WaitForMultipleObjectsEx+0xe9
02 00000052`d5d7f8d0 00007ffc`d1a40f4a KERNELBASE!WaitForMultipleObjects+0xe
03 00000052`d5d7f910 00007ffc`d1a40eb2 coreclr!ds_ipc_poll+0x7e
04 00000052`d5d7fb90 00007ffc`d1a40d74 coreclr!ds_ipc_stream_factory_get_next_available_stream+0x12a
05 00000052`d5d7fc60 00007ffd`d966257d coreclr!server_thread+0x54
06 00000052`d5d7fcd0 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
07 00000052`d5d7fd00 00000000`00000000 ntdll!RtlUserThreadStart+0x28
3 Id: 51a0.1928 Suspend: 0 Teb: 00000052`d54f4000 Unfrozen ".NET Debugger"
# Child-SP RetAddr Call Site
00 00000052`d5eff388 00007ffd`d7ed0479 ntdll!NtWaitForMultipleObjects+0x14
01 00000052`d5eff390 00007ffc`d1a30088 KERNELBASE!WaitForMultipleObjectsEx+0xe9
02 00000052`d5eff670 00007ffc`d1a2f571 coreclr!DebuggerRCThread::MainLoop+0xe8
03 00000052`d5eff730 00007ffc`d1a300fb coreclr!DebuggerRCThread::ThreadProc+0x139
04 00000052`d5eff790 00007ffd`d966257d coreclr!DebuggerRCThread::ThreadProcStatic+0x5b
05 00000052`d5eff7c0 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
06 00000052`d5eff7f0 00000000`00000000 ntdll!RtlUserThreadStart+0x28
4 Id: 51a0.1ef4 Suspend: 0 Teb: 00000052`d54f6000 Unfrozen ".NET Finalizer"
# Child-SP RetAddr Call Site
00 00000052`d607efa8 00007ffd`d7ed0479 ntdll!NtWaitForMultipleObjects+0x14
01 00000052`d607efb0 00007ffc`d1a2ea21 KERNELBASE!WaitForMultipleObjectsEx+0xe9
02 00000052`d607f290 00007ffc`d1a2e873 coreclr!FinalizerThread::WaitForFinalizerEvent+0x6d
03 00000052`d607f2d0 00007ffc`d1a289dd coreclr!FinalizerThread::FinalizerThreadWorker+0x53
04 (Inline Function) --------`-------- coreclr!ManagedThreadBase_DispatchInner+0xd
05 00000052`d607f520 00007ffc`d1a288f3 coreclr!ManagedThreadBase_DispatchMiddle+0x85
06 00000052`d607f600 00007ffc`d1a57231 coreclr!ManagedThreadBase_DispatchOuter+0xab
07 (Inline Function) --------`-------- coreclr!ManagedThreadBase_NoADTransition+0x28
08 (Inline Function) --------`-------- coreclr!ManagedThreadBase::FinalizerBase+0x28
09 00000052`d607f6a0 00007ffd`d966257d coreclr!FinalizerThread::FinalizerThreadStart+0x91
0a 00000052`d607f7b0 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
0b 00000052`d607f7e0 00000000`00000000 ntdll!RtlUserThreadStart+0x28
以下に注目します。これは、ConsoleApp2 モジュールの、Main という関数 (メソッド) から、Calculate 関数が呼ばれ、そこからさらに Plus 関数が呼ばれ、そこから Sleep が呼ばれるというのを表しています。下から上に見ていくのですね。
---一部抜粋---
03 00000052`d577e810 00007ffc`d1a2aefb coreclr!Thread::UserSleep+0xd2
04 00000052`d577e870 00007ffc`cf28e751 coreclr!ThreadNative::Sleep+0x9b
05 00000052`d577e9c0 00007ffc`71f11b1e System_Private_CoreLib!System.Threading.Thread.Sleep+0x11
06 00000052`d577e9f0 00007ffc`71f11a1a ConsoleApp2!ConsoleApp2.Program.Plus+0x9e
07 00000052`d577ea40 00007ffc`71f11977 ConsoleApp2!ConsoleApp2.Program.Calculate+0x3a
08 00000052`d577ea90 00007ffc`d1a9a333 ConsoleApp2!ConsoleApp2.Program.Main+0x47
Main 関数のところで右端に “+0x47” という記載がありますが、これはこの関数の先頭からの相対アドレス(要するにオフセット)かと思います。0x47 は、10 進数で 71 ですが、以下のように、k コマンドでソースコード付きでスタックをみてみると、ソースコード行とは一致しません。意味的には、アセンブラーのレベルで、ベースから 0x47 進んだところで次の関数を Call するという感じかと思います。
03 00000052`d577e810 00007ffc`d1a2aefb coreclr!Thread::UserSleep+0xd2 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 4179]
04 00000052`d577e870 00007ffc`cf28e751 coreclr!ThreadNative::Sleep+0x9b [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 472]
05 00000052`d577e9c0 00007ffc`71f11b1e System_Private_CoreLib!System.Threading.Thread.Sleep+0x11 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 368]
06 00000052`d577e9f0 00007ffc`71f11a1a ConsoleApp2!ConsoleApp2.Program.Plus+0x9e [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 32]
07 00000052`d577ea40 00007ffc`71f11977 ConsoleApp2!ConsoleApp2.Program.Calculate+0x3a [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 19]
08 00000052`d577ea90 00007ffc`d1a9a333 ConsoleApp2!ConsoleApp2.Program.Main+0x47 [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 11]
処理を追加して比較する
では検証のために、試しにCalculate メソッド内を以下のようにしてどうなるか見てみましょう。
using System.Diagnostics;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
int result = Calculate(7, 3);
Console.WriteLine("Log Info : final result = {0}", result);
Console.WriteLine("Sayonara, World!");
}
static int Calculate(int a, int b) {
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
Console.WriteLine("in Calculate method");
int res = Plus(a, b);
Console.WriteLine("Log Info in Calculate method: res = {0}",res);
//Debug.WriteLine("Log Info : a = {0}, b = {1}", a, b);
return res * res;
}
static int Plus(int a, int b)
{
Console.WriteLine("Log Info in Plus method: a = {0}, b = {1}", a, b);
//Debug.WriteLine("Log Info : a = {0}, b = {1}", a, b);
Console.WriteLine("Sleep START");
Thread.Sleep(60000);
Console.WriteLine("Sleep END");
return a + b;
}
}
}
同じようにダンプを取得して、解析してみます。比較してみるとわかりやすいのですが、ソースコードの行について、Calculate 関数と Plus 関数から次の関数を呼ぶところは増えました。当然そうなるかと思います。一方で、オフセットを見ると、変化したのは、あくまでも修正を入れた、Calculate 関数のみである事がわかります。0x128 に増えましたね。そして、Plus 関数については、オフセットは変化していません。0x9e のままです。このことから、+0x というのは、当該メソッドにおけるオフセットだと思われます。(ただし、検証してみないとわからないのですが、シンボルが解決できていない時、要するにモジュール名だけが表示されて関数名が表示されず、その後ろに、ダイレクトに +0x と書かれているときは少し意味が異なるかもしれません。もしかするとその場合は、当該メソッドではなく、当該モジュールにおけるオフセットになるのかもしれません。実際シンボルが解決できていない時は、オフセットの値がでかくなるように見えるので、その点からも辻褄はあいますが、別の機会に検証してみようと思います)
0:000> k
# Child-SP RetAddr Call Site
00 00000079`72b7e4c8 00007ffd`dab851b3 ntdll!NtDelayExecution+0x14
01 00000079`72b7e4d0 00007ffd`d7eb54fd ntdll!RtlDelayExecution+0x43
02 00000079`72b7e500 00007ffc`d007b022 KERNELBASE!SleepEx+0x7d
03 00000079`72b7e580 00007ffc`d007aefb coreclr!Thread::UserSleep+0xd2 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 4179]
04 00000079`72b7e5e0 00007ffc`cf61e751 coreclr!ThreadNative::Sleep+0x9b [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 472]
05 00000079`72b7e730 00007ffc`70571c0e System_Private_CoreLib!System.Threading.Thread.Sleep+0x11 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 368]
06 00000079`72b7e760 00007ffc`70571b08 ConsoleApp2!ConsoleApp2.Program.Plus+0x9e [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 47]
07 00000079`72b7e7b0 00007ffc`70571977 ConsoleApp2!ConsoleApp2.Program.Calculate+0x128 [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 34]
08 00000079`72b7e800 00007ffc`d00ea333 ConsoleApp2!ConsoleApp2.Program.Main+0x47 [C:\Users\xxx\source\repos\ConsoleApp2\ConsoleApp2\Program.cs @ 11]
09 00000079`72b7e840 00007ffc`d0017c39 coreclr!CallDescrWorkerInternal+0x83 [D:\a\_work\1\s\src\coreclr\vm\amd64\CallDescrWorkerAMD64.asm @ 100]
0a (Inline Function) --------`-------- coreclr!CallDescrWorkerWithHandler+0x56 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 67]
0b 00000079`72b7e880 00007ffc`d00d7ea8 coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 570]
0c (Inline Function) --------`-------- coreclr!MethodDescCallSite::Call+0xb [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 458]
0d 00000079`72b7e9c0 00007ffc`d0075f4e coreclr!RunMainInternal+0x11c [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1304]
0e 00000079`72b7eae0 00007ffc`d0076283 coreclr!RunMain+0xd2 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1375]
0f 00000079`72b7eb90 00007ffc`d0075471 coreclr!Assembly::ExecuteMainMethod+0x1bf [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1504]
10 00000079`72b7ee60 00007ffc`d0091a08 coreclr!CorHost2::ExecuteAssembly+0x281 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349]
11 00000079`72b7efd0 00007ffd`767e29d6 coreclr!coreclr_execute_assembly+0xd8 [D:\a\_work\1\s\src\coreclr\dlls\mscoree\exports.cpp @ 504]
12 (Inline Function) --------`-------- hostpolicy!coreclr_t::execute_assembly+0x2a [D:\a\_work\1\s\src\native\corehost\hostpolicy\coreclr.cpp @ 109]
13 00000079`72b7f070 00007ffd`767e2cbc hostpolicy!run_app_for_context+0x596 [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 256]
14 00000079`72b7f200 00007ffd`767e35fa hostpolicy!run_app+0x3c [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 285]
15 00000079`72b7f240 00007ffd`9c7bb5c9 hostpolicy!corehost_main+0x15a [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 426]
16 00000079`72b7f340 00007ffd`9c7be006 hostfxr!execute_app+0x2e9 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145]
17 00000079`72b7f440 00007ffd`9c7c028c hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 532]
18 00000079`72b7f530 00007ffd`9c7be5e4 hostfxr!fx_muxer_t::handle_exec_host_command+0x16c [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 1007]
19 00000079`72b7f5e0 00007ffd`9c7b85a0 hostfxr!fx_muxer_t::execute+0x494 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 578]
1a 00000079`72b7f720 00007ff7`10def9d8 hostfxr!hostfxr_main_startupinfo+0xa0 [D:\a\_work\1\s\src\native\corehost\fxr\hostfxr.cpp @ 62]
1b 00000079`72b7f820 00007ff7`10defde6 ConsoleApp2_exe!exe_start+0x878 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 240]
1c 00000079`72b7f9f0 00007ff7`10df1328 ConsoleApp2_exe!wmain+0x146 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 311]
1d (Inline Function) --------`-------- ConsoleApp2_exe!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90]
1e 00000079`72b7fa60 00007ffd`d966257d ConsoleApp2_exe!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
1f 00000079`72b7faa0 00007ffd`dab8aa58 kernel32!BaseThreadInitThunk+0x1d
20 00000079`72b7fad0 00000000`00000000 ntdll!RtlUserThreadStart+0x28
左側が変更前で、右側が変更後です。
まとめ
ダンプ解析においてスレッドのスタックを見た際の、右端に出る +0x– というものが何なのかについて考察してみました。
コメント