欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何反向繪制出?.NET程序?異步方法調(diào)用棧(最新)

 更新時間:2025年05月12日 09:10:07   作者:一線碼農(nóng)  
這篇文章主要介紹了如何反向繪制出.NET程序異步方法調(diào)用棧(最新),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

一:背景

1. 講故事

這個問題源于給訓(xùn)練營里的一位朋友分析的卡死dump,在分析期間我需要知道某一個異步方法的調(diào)用棧,但程序是 .framework 4.8 ,沒有sos后續(xù)版本獨有的 !dumpasync 命令,所以這就比較搞了,但轉(zhuǎn)念一想,既然 !dumpasync 能把調(diào)用棧搞出來,按理說我也可以給他撈出來,所以就有了此篇。

二:異步調(diào)用棧研究

1. 一個簡單的案例

為了模擬的真實一點,搞一個簡單的三層架構(gòu),最后在 DAL 層的 ReadAsync 之后給它斷住,參考代碼如下:

namespace Example_18_1_1.UI
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task.Run(() =>
            {
                var task = GetCustomersAsync();
                Console.WriteLine(task.IsCompleted);
            });
            Console.ReadLine();
        }
        static async Task GetCustomersAsync()
        {
            string connectionString = @"Server=(localdb)\MyInstance;Database=MyDatabase;Integrated Security=true;";
            try
            {
                Console.WriteLine("Starting async database query...");
                // 初始化服務(wù)
                var customerService = new CustomerService(connectionString);
                // 獲取并顯示客戶數(shù)據(jù)
                var customers = await customerService.GetCustomersForDisplayAsync();
                foreach (var customer in customers)
                {
                    Console.WriteLine($"Customer: ID={customer.Id}, Name={customer.Name}");
                }
                Console.WriteLine("Query completed successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
}
namespace Example_18_1_1.BLL
{
    public class CustomerService
    {
        private readonly CustomerRepository _repository;
        public CustomerService(string connectionString)
        {
            _repository = new CustomerRepository(connectionString);
        }
        public async Task<IEnumerable<Customer>> GetCustomersForDisplayAsync()
        {
            // 這里可以添加業(yè)務(wù)邏輯,如驗證、轉(zhuǎn)換等
            var customers = await _repository.GetTop10CustomersAsync();
            // 示例業(yè)務(wù)邏輯:確保名稱不為null
            foreach (var customer in customers)
            {
                customer.Name ??= "Unknown";
            }
            return customers;
        }
    }
}
namespace Example_18_1_1.DAL
{
    public class CustomerRepository
    {
        private readonly string _connectionString;
        public CustomerRepository(string connectionString)
        {
            _connectionString = connectionString;
        }
        public async Task<IEnumerable<Customer>> GetTop10CustomersAsync()
        {
            var customers = new List<Customer>();
            await using (var connection = new SqlConnection(_connectionString))
            {
                await connection.OpenAsync();
                var command = new SqlCommand("SELECT TOP 10 * FROM Customers", connection);
                await using (var reader = await command.ExecuteReaderAsync())
                {
                    while (await reader.ReadAsync())
                    {
                        customers.Add(new Customer
                        {
                            Id = Convert.ToInt32(reader["Id"]),
                            Name = Convert.ToString(reader["Name"])
                        });
                        Debugger.Break();
                    }
                }
            }
            return customers;
        }
    }
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

從代碼流程看,異步調(diào)用鏈?zhǔn)沁@樣的 GetCustomersAsync -> GetCustomersForDisplayAsync -> GetTop10CustomersAsync 一個過程,在程序中斷之后,我們用 WinDbg 附加,使用 !clrstack 觀察當(dāng)前調(diào)用棧。

0:017> !clrstack
OS Thread Id: 0x3118 (17)
        Child SP               IP Call Site
000000ABD6CBEAF8 00007ffeb1e61db2 [HelperMethodFrame: 000000abd6cbeaf8] System.Diagnostics.Debugger.BreakInternal()
000000ABD6CBEC00 00007ffdf818a91a System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
000000ABD6CBEC30 00007ffd9915079d Example_18_1_1.DAL.CustomerRepository+d__2.MoveNext() [D:\skyfly\18.20220727\src\Example\Example_18_1_1\Program.cs @ 115]
000000ABD6CBEE50 00007ffdf827f455 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].ExecutionContextCallback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 286]
000000ABD6CBEE80 00007ffdf808dde9 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
000000ABD6CBEEF0 00007ffdf827f593 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 324]
000000ABD6CBEF60 00007ffdf827f4ec System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext() [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 302]
000000ABD6CBEF90 00007ffdf80a9a06 System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Runtime.CompilerServices.IAsyncStateMachineBox, Boolean) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs @ 795]
000000ABD6CBEFF0 00007ffdf80a48eb System.Threading.Tasks.Task.RunContinuations(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3374]
000000ABD6CBF0D0 00007ffdf80a4866 System.Threading.Tasks.Task.FinishContinuations() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3350]
000000ABD6CBF110 00007ffdf8251350 System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib]].TrySetResult(System.__Canon) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @ 400]
000000ABD6CBF160 00007ffdf8254fc3 System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].TrySetFromTask(System.Threading.Tasks.Task, Boolean)
000000ABD6CBF1C0 00007ffdf825515b System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].ProcessInnerTask(System.Threading.Tasks.Task) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 6940]
000000ABD6CBF200 00007ffdf8254ead System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].ProcessCompletedOuterTask(System.Threading.Tasks.Task)
000000ABD6CBF240 00007ffdf8254d1b System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].Invoke(System.Threading.Tasks.Task) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 6802]
000000ABD6CBF280 00007ffdf80a4e11 System.Threading.Tasks.Task.RunOrQueueCompletionAction(System.Threading.Tasks.ITaskCompletionAction, Boolean)
000000ABD6CBF2C0 00007ffdf80a4c0a System.Threading.Tasks.Task.RunContinuations(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3392]
000000ABD6CBF3A0 00007ffdf80a4866 System.Threading.Tasks.Task.FinishContinuations() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3350]
000000ABD6CBF3E0 00007ffdf80a2e9f System.Threading.Tasks.Task.FinishStageThree() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2125]
000000ABD6CBF410 00007ffdf80a2d0b System.Threading.Tasks.Task.FinishStageTwo() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2095]
000000ABD6CBF460 00007ffdf80a33f6 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2350]
000000ABD6CBF500 00007ffdf80a3293 System.Threading.Tasks.Task.ExecuteEntryUnsafe(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2271]
000000ABD6CBF540 00007ffdf80a323a System.Threading.Tasks.Task.ExecuteFromThreadPool(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2262]
000000ABD6CBF570 00007ffdf80969df System.Threading.ThreadPoolWorkQueue.Dispatch()
000000ABD6CBF610 00007ffdf809e566 System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @ 107]
000000ABD6CBF730 00007ffdf8082f0f System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
000000ABD6CBF9C0 00007ffdf8ccbde3 [DebuggerU2MCatchHandlerFrame: 000000abd6cbf9c0] 

卦中真的是眼花繚亂,找瞎了眼也沒找到調(diào)用鏈上的三個方法名,只有一個 Example_18_1_1.DAL.CustomerRepository+d__2 狀態(tài)機類,經(jīng)過 ILSpy反編譯才能勉強的看到是 GetTop10CustomersAsync 方法,截圖如下:

所以sos為了讓調(diào)試者免去這個痛苦,新增了 !dumpasync 命令。

0:017> !dumpasync
STACK 1
0000028b00029338 00007ffd993d1e00 (-1) Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2 @ 7ffd991502a0
  0000028b00029438 00007ffd993d3290 (0) Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2 @ 7ffd9914d6c0
    0000028b00029550 00007ffd993d3fe8 (0) Example_18_1_1.UI.Program+<GetCustomersAsync>d__1 @ 7ffd9914b8f0

雖然能以 屏蔽外部代碼 的方式顯示出了異步調(diào)用棧,但這個sos 命令是 .netcore 獨有的,所以作為高級調(diào)試者,我們必須具有手工繪制的能力。

2. 如何手工繪制

要想手工繪制,需要了解異步狀態(tài)機的內(nèi)部機制,即子函數(shù)和父函數(shù)是通過 m_continuationObject 字段串聯(lián)的,去年我寫過一篇關(guān)于異步方法串聯(lián)的文章,可以參考下 (http://www.dbjr.com.cn/program/341250jj0.htm)[聊一聊 C#異步 任務(wù)延續(xù)的三種底層玩法],這里就不具體說了,用一張圖來表示吧。

本質(zhì)上來說就是 Box 之間形成了一個跨線程的由m_continuationObject串聯(lián)出的單鏈表,有了思路之后,我們開始驗證吧,使用 !dso 找到頭節(jié)點 box。

0:017> !dso
OS Thread Id: 0x3118 (17)
          SP/REG           Object Name
             rbx     028b00029338 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Collections.Generic.IEnumerable<Example_18_1_1.DAL.Customer>>+AsyncStateMachineBox<Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2>
....
0:017> !dumpobj /d 28b00029338
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2, Example_18_1_1]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffd99125690  4000db9       20        System.Object  0 instance 0000028b00029438 m_continuationObject
...
0:017> !DumpObj /d 0000028b00029438
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2, Example_18_1_1]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffd99125690  4000db9       20        System.Object  0 instance 0000028b00029550 m_continuationObject
...
0:017> !DumpObj /d 0000028b00029550
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Example_18_1_1.UI.Program+<GetCustomersAsync>d__1, Example_18_1_1]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffd99125690  4000db9       20        System.Object  0 instance 0000000000000000 m_continuationObject
...
00007ffd99125708  4001337       48       System.__Canon  0 instance 0000028b0000e7f8 StateMachine
...

上面三個 m_continuationObject 值即是 !dumpasync 輸出的結(jié)果,最后一個 m_continuationObject=null 說明為異步執(zhí)行流的最后一個節(jié)點,流程正在這里沒出來,可以把這個異步狀態(tài)機給解包出來,即卦中的 StateMachine 字段,輸出如下:

0:017> !do 0000028b0000e7f8
Name:        Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd991c94b0  4000018       30         System.Int32  1 instance                0 <>1__state
00007ffd9924fca0  4000019       38 ...Private.CoreLib]]  1 instance 0000028b0000e830 <>t__builder
00007ffd99247298  400001a        8 ...L.CustomerService  0 instance 0000028b0000e7c8 <>4__this
00007ffd992453b0  400001b       10 ... Example_18_1_1]]  0 instance 0000000000000000 <customers>5__1
00007ffd992453b0  400001c       18 ... Example_18_1_1]]  0 instance 0000000000000000 <>s__2
00007ffd99246d60  400001d       20 ... Example_18_1_1]]  0 instance 0000000000000000 <>s__3
00007ffd99245338  400001e       28 ..._1_1.DAL.Customer  0 instance 0000000000000000 <customer>5__4
00007ffd99245448  400001f       40 ...Private.CoreLib]]  1 instance 0000028b0000e838 <>u__1

再配上 ILSpy 反編譯出來的狀態(tài)機代碼,截圖如下:

可以根據(jù)這里面的字段賦值情況來推測當(dāng)前正執(zhí)行哪一個階段。

3. 父節(jié)點如何找到子節(jié)點

剛才我們是通過 子節(jié)點 -> 父節(jié)點 尋找法,在真實的dump分析中,可能還會存在反向的情況,即 父節(jié)點 -> 子節(jié)點 尋找法,但父節(jié)點尋找目標(biāo)子節(jié)點的過程中會存在多條鏈路,比如 GetTop10CustomersAsync 方法中存在五個 await 就對應(yīng)著 4條鏈路。

用狀態(tài)機的話術(shù)就是下面的4個 <>u__xxxx

可能有些朋友還是有點懵,沒關(guān)系,我也繪制一張圖。

最后通過 windbg 來驗證一下。

0:017> !do 0000028b00029550 
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Example_18_1_1.UI.Program+<GetCustomersAsync>d__1, Example_18_1_1]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99125708  4001337       48       System.__Canon  0 instance 0000028b0000de10 StateMachine
0:017> !DumpObj /d 0000028b0000de10
Name:        Example_18_1_1.UI.Program+<GetCustomersAsync>d__1
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99245448  400002b       50 ...Private.CoreLib]]  1 instance 0000028b0000de60 <>u__1
0:017> !DumpVC /d 00007ffd99245448 0000028b0000de60
Name:        System.Runtime.CompilerServices.TaskAwaiter`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99247db8  400139e        0 ...Private.CoreLib]]  0 instance 0000028b00029438 m_task
0:017> !DumpObj /d 0000028b00029438
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2, Example_18_1_1]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99125708  4001337       48       System.__Canon  0 instance 0000028b0000e7f8 StateMachine
0:017> !DumpObj /d 0000028b0000e7f8
Name:        Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2
00007ffd99245448  400001f       40 ...Private.CoreLib]]  1 instance 0000028b0000e838 <>u__1
0:017> !DumpVC /d 00007ffd99245448 0000028b0000e838
Name:        System.Runtime.CompilerServices.TaskAwaiter`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib]]
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99247db8  400139e        0 ...Private.CoreLib]]  0 instance 0000028b00029338 m_task
0:017> !DumpObj /d 0000028b00029338
Name:        System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2, Example_18_1_1]]
MethodTable: 00007ffd993d1e00
EEClass:     00007ffd993c1810
Tracked Type: false
Size:        96(0x60) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.36\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffd99125708  4001337       48       System.__Canon  0 instance 0000028b0000e870 StateMachine
0:017> !DumpObj /d 0000028b0000e870
Name:        Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffd992602f0  4000014       60 ...vices.TaskAwaiter  1 instance 0000028b0000e8d0 <>u__1
00007ffd99267a60  4000015       68 ....Data.SqlClient]]  1 instance 0000028b0000e8d8 <>u__2
00007ffd99260450  4000016       70 ...Private.CoreLib]]  1 instance 0000028b0000e8e0 <>u__3
00007ffd99260ae8  4000017       78 ....ValueTaskAwaiter  1 instance 0000028b0000e8e8 <>u__4

4. 有沒有更快捷的方式

手工繪制雖然是兜底方案,但每次都要這樣搞也確實太累,所以最近我在思考有沒有更好的方式,好巧不巧,昨天在知乎上刷到了這樣的一篇文章,hez2010大佬的話突然點醒了我,截圖如下:

哈哈,點醒了我什么呢?即 sos 解析托管代碼的能力遠(yuǎn)不如官方的 Visual Studio,畢竟后者才是全球最專業(yè)的托管代碼調(diào)試器,將生成好的dump丟到 VS 中,在 Stack 或者 Parallel Stack 中一定要屏蔽 外部代碼(External Code) ,否則海量的 AsyncTaskMethodBuilder 和 MoveNext 會淹死我們,截圖如下:

三:總結(jié)

手工繪制異步調(diào)用棧需要對異步的底層構(gòu)建有一個清晰的認(rèn)識,調(diào)試師是痛苦的,要想進(jìn)階為資深,需要日積月累的底層知識沉淀,在自我學(xué)習(xí)的過程中如果沒有無數(shù)次的在絕望中尋找希望的能力,很容易從入門到放棄。。。

到此這篇關(guān)于如何反向繪制出 .NET程序 異步方法調(diào)用棧的文章就介紹到這了,更多相關(guān).NET程序 異步方法調(diào)用棧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論