[Debug 006] スタックの読み方(右端の “+0x” の意味)

概要

今日はスタックの読み方について考察します。スタックを分析した時に、”+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 するという感じかと思います。

オフセット (コンピュータ) – Wikipedia

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– というものが何なのかについて考察してみました。

コメント