概述:該通用單例泛型基類使用C#實現,線程安全,通過泛型參數和Lazy<T>實現簡化的單例模式。優點包括線程安全、泛型通用性、簡化實現、以及延遲載入的特性。 優點: 線程安全: 使用Lazy<T>確保了線程安全的延遲初始化,避免了在多線程環境下可能導致的競態條件問題。 泛型通用性: 通過泛型參數,該 ...
一、簡介
這是我的《Advanced .Net Debugging》這個系列的第三篇文章。這個系列的每篇文章寫的周期都要很長,因為每篇文章都是原書的一章內容(太長的就會分開寫)。再者說,原書寫的有點早,有些內容還是需要修正的,調試每個案例,這都是需要時間的。今天這篇文章的標題雖然叫做“基本調試任務”,但是這章的內容還是挺多的。我本來想用一篇文章把這個章節寫完,我發現是不可能的,於是就分“上“和”下”用兩篇來寫。既然,我們要調試我們的 .Net 應用程式,那必須掌握一些調試技巧、方法和工具。我們習慣了使用 Visual Studio IDE 的調試技巧,比如:單步調試、下斷點、過程調試等,但是,有些時候,VS 是使用不了的。那我們也必須學習如何使用 Windbg 的命令,在沒有 VS IDE 的情況下,如何調試我們的程式,如何設置斷點、恢復執行、中斷執行、退出調試回話,如何為 JIT 編譯的方法設置斷點,如何為沒有被 JIT 編譯的方法設置斷點,為泛型方法設置斷點等等。如果我們想成為一名合格程式員,這些調試技巧都是必須要掌握的。
如果在沒有說明的情況下,所有代碼的測試環境都是 Net 8.0,如果有變動,我會在項目章節里進行說明。好了,廢話不多說,開始我們今天的調試工作。
調試環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
操作系統:Windows Professional 10
調試工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)和 NTSD(10.0.22621.2428 AMD64)
下載地址:可以去Microsoft Store 去下載
開發工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
Net 版本:.Net 8.0
CoreCLR源碼:源碼下載
說明一下,這個系列內容安排有些變動,我把基礎知識和眼見為實放在了一起,講什麼內容,立刻就將講的內容做一個眼見為實驗證,這樣做更便於大家理解,我認為這樣會更好一些,不用在文章里來回跑了。
命令行調試器要想成功使用,必須先安裝 MSVC,想要瞭解詳情,可以去微軟的官網:https://learn.microsoft.com/zh-cn/cpp/build/building-on-the-command-line?view=msvc-170,如果我們使用的 Visual Studio 2022,本身也有命令行工具,我們就可以直接使用。安裝如圖:
二、調試源碼
廢話不多說,本節是調試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。
2.1、ExampleCore_3_1_1
1 namespace ExampleCore_3_1_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine("Welcome to Advanced .Net Debugging!"); 8 Console.Read(); 9 } 10 } 11 }
2.2、ExampleCore_3_1_2
1 using System.Diagnostics; 2 3 namespace ExampleCore_3_1_2 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Console.WriteLine("第一次執行,並開始中斷執行!"); 10 Debugger.Break(); 11 Console.WriteLine("第二次執行,並開始中斷執行!"); 12 Debugger.Break(); 13 Console.WriteLine("第三次執行,並開始中斷執行!"); 14 Debugger.Break(); 15 16 Console.WriteLine("恢復執行調試完畢!"); 17 Console.ReadLine(); 18 } 19 } 20 }
2.3、ExampleCore_3_1_3
1 using System.Diagnostics; 2 3 namespace ExampleCore_3_1_3 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Sum1(10); 10 Debugger.Break(); 11 12 int i = 10; 13 int j = 20; 14 15 var sum = Sum1(i); 16 Console.WriteLine($"sum={sum},i={i},j={j}"); 17 18 Console.ReadLine(); 19 } 20 21 private static int Sum1(int a) 22 { 23 var i = a; 24 var j = 11; 25 int sum = Sum2(i, j); 26 27 return sum; 28 } 29 30 private static int Sum2(int a, int b) 31 { 32 var i = a; 33 var j = b; 34 var k = 13; 35 36 var sum = Sum3(i, j, k); 37 return sum; 38 } 39 40 private static int Sum3(int i, int j, int k) 41 { 42 return i + j + k; 43 } 44 } 45 }
2.4、ExampleCore_3_1_4
1 namespace ExampleCore_3_1_4 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 //第一次調用函數 8 Console.WriteLine("Press any key(1st instance function)"); 9 Console.ReadKey(); 10 BreakPoint bp = new BreakPoint(); 11 bp.AddAndPrint(10, 5); 12 13 //第二次調用函數 14 Console.WriteLine("Press any key(2nd instance function)"); 15 Console.ReadKey(); 16 bp = new BreakPoint(); 17 bp.AddAndPrint(100, 50); 18 } 19 } 20 21 internal class BreakPoint 22 { 23 public void AddAndPrint(int a, int b) 24 { 25 int res = a + b; 26 Console.WriteLine("Adding {0}+{1}={2}", a, b, res); 27 } 28 } 29 }
2.5、ExampleCore_3_1_5
1 using System.Diagnostics; 2 3 namespace ExampleCore_3_1_5 4 { 5 internal class Program 6 { 7 static void Main(string[] args) 8 { 9 Debugger.Break(); 10 11 var mylist = new MyList<int>(); 12 13 mylist.Add(10); 14 15 Console.ReadLine(); 16 } 17 } 18 19 public class MyList<T> 20 { 21 public T[] arr = new T[10]; 22 23 public void Add(T t) 24 { 25 arr[0] = t; 26 } 27 } 28 }
三、基礎知識和眼見為實
3.1、調試器以及調試目標
A、知識介紹
在任何調試中都包含兩個組件:調試器和調試目標。
調試器:是一個引擎,我們必須通過這個引擎和調試目標進行交互。所有與調試目標之間的交互操作(如:設置斷點、觀察狀態等),都可以通過調試器的命令完成,而調試器將在調試目標的環境中執行這些命令。
調試目標:一般指我們編寫的程式,對於 .Net程式員來說就是,或者是要調試的程式。
它們之間的關係,有一張圖可以更好的表現他們之間的關係。如圖:
B、眼見為實
在【眼見為實】這個章節里,有些調試動作是一樣的,我就不每個節點都寫了。我就寫在這裡了。首先編譯好自己的項目,根據自己的喜好,可以切換到編譯項目目錄下,也可以直接輸入項目所在目錄,接著就可以進行項目調試了。我使用的命令行工具是【Developer Command Prompt for VS 2022】。
1)、使用NTSD調試器
調試源碼:ExampleCore_3_1_1
我們命令行工具中輸入命令:ntsd,打開新視窗。效果如圖:
NTSD 的新視窗。
如果沒有指定任何參數,只能顯示一組可用的選項。我們將我們的項目完整路徑和項目名稱作為輸入參數。執行【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\ExampleCore_3_1_1.exe】,彈出新視窗,如圖:
新的 NTSD 視窗,如圖:
上面這個截圖分三個部分:第一部分是符號文件搜索路徑,如圖:
第二部分:載入的所有模塊,表示應用程式所需要的模塊都已經載入完畢。如圖:
第三部分:中斷指令異常。每當調試器啟動一個進程或者調試器附加到一個進程的時候,調試器都會註入一個中斷指令,這條指令將使調試目標停止運行。斷點指令的作用:使用戶與調試器和調試目標進行交互。這裡是 int 3 中斷。如圖:
調試器的命令提示符是:X:Y>,X 表示當前正在被調試的活動目標(在大多數調試器中,這個值為 0),Y 表示導致調試器中斷的線程 ID。如圖:
2)、使用 NTSD 附加進程
調試源碼:ExampleCore_3_1_1
當在調試器下啟動有問題的引用程式的時候,這種調試方式很有作用。如果是引用程式已經運行起來了,那我們該如何調試呢?我們可以通過給【ntsd】命令,加上 -p 命令,就可以附加進程了。比如:你寫的一個 Web 服務已經成功運行起來了。隨著時間的推移,這個 Web 服務開始表現出一些怪怪的行為,你希望當程式具有這總奇怪行為的時候對它進行調試,附加調試就可以大展拳腳了。
-p 參數告訴調試器希望調試一個正在運行的進程。對於這個參數後,再跟上要調試進程的 Id 就可以了。
執行命令【ntsd -p 14624】,如圖:
打開新的調試器視窗,如圖:
下麵很有很多內容,就不顯示了。
3)、使用 TList.exe 顯示進程 Id。
調試源碼:ExampleCore_3_1_1
如果我們想獲取一個進程的 id,可以有很多方法,我們可以使用 Windows 調試工具集中的 tlist.exe,tlist.exe 會輸出所有運行的進程名稱和 ID。啟動我們的 ExampleCore_3_1_1.exe,在控制台輸出:Welcome to Advanced .Net Debugging!,我們打開命令工具【Developer Command Prompt for vs 2022】,輸出命令 tlist,顯示如下:
我們進程的信息如下:
4)、使用 Windbg 調試
調試源碼:ExampleCore_3_1_1
編譯好我們的項目,打開【Windbg Preview】調試器。依次點擊【文件】--->【Launch executable】載入我們的項目文件:ExampleCore_3_1_1.exe,選擇【打開】按鈕,成功載入併進入調試器界面。
點擊【文件】按鈕,切換界面。
點擊【Launch executable】按鈕打開新視窗。如圖:
進入調試器界面,如圖:
我們就可以在下方的命令框中輸入命令,調試程式了。如圖:
以上是在調試器中啟動有問題的引用程式的流程,如果想使用【Windbg Preview】附加進程該怎麼辦呢?其實,也很簡單,編譯項目,打開調試器,依次點擊【文件】--->【Attach to process】,如圖:
打開選擇進程的視窗,在右側。
之後就是進入調試器界面,使用方法就一樣了。
3.2、符號
A、知識介紹
符號文件:是一種輔助數據,它包含了對引用程式代碼的一些標註信息,這些信息在調試過程中非常有用。如果沒有這些輔助數據,那獲得的信息只有引用程式的二進位文件了。對二進位代碼進行調試是非常困難的,因為你無法看到代碼中的函數名、數據結構名等。符號文件的擴展名通常是 .pdb。
在符號文件中包含很多重要的信息,例如:行號和局部變數的名稱等,它能極大的提高調試的效率。
符號文件有兩種類型:私有符號文件(Private)和公有符號文件(Public)。
私有符號文件:是大多數開發人員在日常工作中使用的符號文件,其中包含了調試會話中所需要的所有符號信息。私有符號文件一般是和我們編譯的程式存放在一起的,調試器在開始調試的時候,會自動載入他們。如圖:
公有符號文件:這類型的符號文件只是有選擇的包含了一些符號信息,這會使調試工作困難一點。比如:在 Microsoft 符號伺服器上存儲了一些公有符號文件。每當將調試器指向 Microsoft 符號伺服器時,都可以下載這些符號文件,併在調試會話中使用它們。
之所以有【私有符號文件】和【公有符號文件】之分,主要是為了保護知識產權。私有符號中包含大量底層技術信息,就很容易對應用程式進行逆向工程。公有符號就不存在這樣的問題,既可以調試,又不會泄露核心技術信息。
B、眼見為實
.sympath(+) 命令的使用,加號表示不會替換,而是追加。
調試源碼:ExampleCore_3_1_1
編譯好我們的項目,打開【Windbg Preview】工具,依次打開【文件】--->【Launch executable】,載入我們的項目文件:ExampleCore_3_1_1.exe,進入調試器。直接執行【.sympath】命令,就可以看到當前的符號文件信息。
1 0:000> .sympath 2 Symbol search path is: srv* 3 Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols 4 5 ************* Path validation summary ************** 6 Response Time (ms) Location 7 Deferred srv*
【.sympath】命令可以設置符號文件路徑。在命令後跟上具體的符號文件所在的地址。
1 0:007> .sympath E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\ 2 Symbol search path is: E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\ 3 Expanded Symbol search path is: e:\visual studio 2022\source\projects\advanceddebug.netframework.test\examplecore_3_1_1\bin\debug\net8.0\ 4 5 ************* Path validation summary ************** 6 Response Time (ms) Location 7 OK E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_1\bin\Debug\net8.0\
雖然,我們設置新的符號文件的路徑,但是並不會從這個路徑中載入任何符號。如果想要載入符號信息,必須執行【.reload】命令。我們還是設置回去吧,防止以後有錯誤。
1 0:007> .sympath srv* 2 Symbol search path is: srv* 3 Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols 4 5 ************* Path validation summary ************** 6 Response Time (ms) Location 7 Deferred srv*
如果我們使用【.sympath】命令設置錯了符號文件地址,使用【.reload】也無法載入符號文件,我們可以使用【.symfix】命令,修複問題就可以了。
1 0:007> .symfix 2 DBGHELP: Symbol Search Path: cache*;SRV*https://msdl.microsoft.com/download/symbols 3 SYMSRV: BYINDEX: 0x17 4 C:\ProgramData\Dbg\sym 5 ntdll.pdb 6 63E12347526A46144B98F8CF61CDED791 7 SYMSRV: PATH: C:\ProgramData\Dbg\sym\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb 8 SYMSRV: RESULT: 0x00000000 9 DBGHELP: ntdll - public symbols 10 C:\ProgramData\Dbg\sym\ntdll.pdb\63E12347526A46144B98F8CF61CDED791\ntdll.pdb 11 SYMSRV: BYINDEX: 0x18 12 C:\ProgramData\Dbg\sym 13 kernel32.pdb 14 85A257DB4B7B82F2E19AD96AB7BB116A1 15 SYMSRV: PATH: C:\ProgramData\Dbg\sym\kernel32.pdb\85A257DB4B7B82F2E19AD96AB7BB116A1\kernel32.pdb 16 SYMSRV: RESULT: 0x00000000 17 DBGHELP: KERNEL32 - public symbols 18 C:\ProgramData\Dbg\sym\kernel32.pdb\85A257DB4B7B82F2E19AD96AB7BB116A1\kernel32.pdb
這裡有這麼多輸出,是因為我執行了【!sym noisy】命令,開啟了顯示符號載入的詳細信息,如果不想顯示,可以使用【!sym quiet 】命令。
1 0:007> !sym quiet 2 quiet mode - symbol prompts on 3 0:007> .symfix【.symfix】命令後面可以跟一個參數,就是本地路徑,用來緩存下載的符號文件,就不用調試器每次去下載相同的符號了,可以直接從本地載入。在預設情況下,如果沒有指定本地路徑緩存,那麼調試器將使用調試軟體包安裝的路徑下的 sym 文件夾。
3.3、控制調試目標的執行
在任何調試會話中,能夠控制調試目標的執行是非常重要的。我們可以設置斷點,然後恢復程式的執行知道斷點處,在此可以查看應用程式的狀態,單步跟蹤到函數內部,然後在恢復執行等。
3.3.1、中斷執行
調試器中斷程式的方式有很多種,我舉三種最常用的中斷執行的方式。
1)、如果我們使用的命令行調試器,可以使用【ctrol+c】組合鍵手動方式中斷調試目標的執行,例如:調試死鎖問題。
2)、給我們的應用程式設置斷點來中斷調試目標的執行。通過設置斷點,可以很方便的使調試器在執行流程的任意位置上中斷執行。
3)、拋出異常可以使調試器中斷執行。
3.3.2、恢復執行
A、知識介紹
當調試器中斷執行時(可能是觸發了斷點或者其他的事件),可以使用【g】命令回覆調試器的執行。如果【g】命令不帶任何參數,只是回覆調試目標的執行,直到下一次發生某個調試事件。
B、眼見為實
1)、使用【g】命令恢復執行。
調試源碼:ExampleCore_3_1_2
編譯好我們的項目,打開我們的命令工具,輸入命令【ntsd E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】,打開調試器新視窗。
打開新的調試器視窗,如圖:
觸發初始斷點是調試器的預設行為,也是調試人員開始分析應用程式的最早時機。此時,調試目標會停止執行並等待輸入命令。此刻,我們可以輸入【g】命令,恢復執行,調試器輸出如圖:
調試源碼:ExampleCore_3_1_2
如果不希望調試器在初始啟動時停止程式的執行,可以在啟動調試器時使用 -g 命令開關,每當調試器退出時,調試器也將停止執行,可以使用 -G(大寫)命令開關,避免在進程結束時觸發最終的斷點。
編譯好我們的項目,打開我們的命令行工具,使用【ntsd -g E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】命令,啟動調試器。如圖:
打開新的調試器視窗,這次已經輸出“第一次執行,並開始中斷執行!”,如圖:
如果我們使用 -G 命令開關,執行命令【ntsd -G E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_3_1_2\bin\Debug\net8.0\ExampleCore_3_1_2.exe】,調試器在初始會中斷執行。如圖:
3.3.3、單步調試代碼
我們使用過 VS IDE 的調試功能,快捷鍵有:F10,F11,F9等,調試器也為我們提供了類似的命令。但是,需要註意:如果在調試托管代碼時使用非托管調試器,那麼通常是對 JIT 編譯器產生的機器代碼進行單步調試。
A、知識介紹
1)、p 命令
p(step):命令其實就是 VS 中的 f10 快捷鍵,單步執行,遇到函數也是當成一條指令執行,不會進入函數體。
2)、t 命令
t(trace):命令其實就是 VS 的 f11 快捷鍵,它是一種進入函數的單步執行調試。
3)、pc 命令
pc(Step to Next Call) 就是一直運行直到遇到 call 為止,不會進入函數體,call 是一個函數調用,彙編指令。
4)、tc 命令
tc(Trace to Next Call) 和 pc 不同的是,tc 會進入方法體,直到遇到 call 為止。
5)、pt 命令
pt(Step to Next Return) 如果有方法會進入方法內部遞歸處理,遇到下一個 ret 為止。
6)、tt 命令
tt(Trace to Next Return) 會進入函數體直到遇到 ret 為止。遞歸的意思。
B、眼見為實
這一節的演示,使用【Windbg Preview】,我沒有使用的是【NSTD】調試器,其他他們是一樣的,只不過一個是由界面的,一個是沒界面的。有些操作是一樣的,我就寫在這裡了。
編譯好我們的項目,打開【Windbg Preview】,依次點擊【文件】--->【Launch executable】,載入我們的項目文件:ExampleCore_3_1_3.exe,進入調試器。界面的內容太多,我們可以使用【.cls】命令,清空調試器的界面。我們再使用【g】命令,繼續運行調試器,我們現在查看一下托管代碼的調用棧,執行命令【!clrstack】。
0:000> g ModLoad: 00007ff9`454c0000 00007ff9`454f0000 C:\Windows\System32\IMM32.DLL ModLoad: 00007ff8`8d8e0000 00007ff8`8d938000 C:\Program Files\dotnet\host\fxr\8.0.0\hostfxr.dll ModLoad: 00007ff8`812d0000 00007ff8`81334000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\hostpolicy.dll ModLoad: 00007ff8`80de0000 00007ff8`812cb000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\coreclr.dll ModLoad: 00007ff9`454f0000 00007ff9`45619000 C:\Windows\System32\ole32.dll ModLoad: 00007ff9`44b10000 00007ff9`44e64000 C:\Windows\System32\combase.dll ModLoad: 00007ff9`45d60000 00007ff9`45e35000 C:\Windows\System32\OLEAUT32.dll ModLoad: 00007ff9`444f0000 00007ff9`4456f000 C:\Windows\System32\bcryptPrimitives.dll (3994.1028): Unknown exception - code 04242420 (first chance) ModLoad: 00007ff8`7fb10000 00007ff8`807a8000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Private.CoreLib.dll ModLoad: 00007ff8`7f950000 00007ff8`7fb08000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\clrjit.dll ModLoad: 00007ff9`44120000 00007ff9`44133000 C:\Windows\System32\kernel.appcore.dll ModLoad: 0000021a`398c0000 0000021a`398c8000 E:\Visual Studio 2022\.\ExampleCore_3_1_3\bin\Debug\net8.0\ExampleCore_3_1_3.dll ModLoad: 0000021a`398d0000 0000021a`398de000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Runtime.dll ModLoad: 00007ff8`7f920000 00007ff8`7f948000 C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.0\System.Console.dll (3994.1028): Break instruction exception - code 80000003 (first chance) KERNELBASE!wil::details::DebugBreak+0x2: 00007ff9`44799202 cc int 3 0:000> !clrstack OS Thread Id: 0x1028 (0) Child SP IP Call Site 00000020B9B7E6A8 00007ff944799202 [HelperMethodFrame: 00000020b9b7e6a8] System.Diagnostics.Debugger.BreakInternal() 00000020B9B7E7B0 00007ff87ff360aa System.Diagnostics.Debugger.Break() [/_/src/coreclr/./Debugger.cs @ 18] 00000020B9B7E7E0 00007ff8213f197f ExampleCore_3_1_3.Program.Main(System.String[]) [E:\Visual Studio 2022\.\ExampleCore_3_1_3\Program.cs @ 10]
我們找到了紅色標註的【Program.Main()】方法的地址:00007ff8213f197f,有了這個地址,我們就可以對這個地址下一個斷點。
1 0:000> bp 00007ff8213f197f
設置好斷點後,我們就可以使用【g】命令,繼續運行調試器。
1)、p、pc、pt 命令的使用
調試源碼:ExampleCore_3_1_3
我們設置好了斷點,就可以開始我們的調試工作了。繼續使用【g】運行調試器,調試器會在【Debugger.Break()】這行代碼暫停,效果如圖:
我們可以使用【p】命令,單步執行,到了第15行代碼,會直接跳過而執行,不會進入方法。當然,這個過程要執行多次【p】命令。
1 0:000> p 2 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x50: 3 00007ff8`213f1980 c745fc0a000000 mov dword ptr [rbp-4],0Ah ss:00000020`b9b7e84c=00000000 4 0:000> p 5 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x57: 6 00007ff8`213f1987 c745f814000000 mov dword ptr [rbp-8],14h ss:00000020`b9b7e848=00000000 7 0:000> p 8 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x5e: 9 00007ff8`213f198e 8b4dfc mov ecx,dword ptr [rbp-4] ss:00000020`b9b7e84c=0000000a 10 0:000> p 11 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x61: 12 00007ff8`213f1991 ff1531520a00 call qword ptr [00007ff8`21496bc8] ds:00007ff8`21496bc8=00007ff8213f1a20 13 0:000> p 14 ExampleCore_3_1_3!ExampleCore_3_1_3.Program.Main+0x67: 15 00007ff8`213f1997 8