قراءة ملفّات تفريغ الأعطال بـ WinDbg وSOS ── مدخل عمليّ إلى التحليل بعد الجمع

· آخر تحديث: · · WinDbg, SOS, ملفّ تفريغ الأعطال, .NET, CSharp, تصحيح الأخطاء, PDB, تحقيق الأعطال, الاستشارات التقنيّة

سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621626)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

غو كومورا (2026). قراءة ملفّات تفريغ الأعطال بـ WinDbg وSOS ── مدخل عمليّ إلى التحليل بعد الجمع. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621626 https://comcomponent.com/ar/blog/windbg-sos-crash-dump-analysis/

DOI (أحدث نسخة)
10.5281/zenodo.21621626
DOI (هذه النسخة)
10.5281/zenodo.22241007

في المقال السابق «مدخل إلى جمع dump انهيار تطبيقات Windows» رتّبنا الأمر حتى مرحلة «جمع» ملفّ التفريغ باستخدام WER LocalDumps وProcDump وMiniDumpWriteDump. لكن مجرّد الحصول على ملفّ التفريغ لا يخبرنا بشيء. لا يصير مادّة للتحقيق إلا بعد أن نعمل بأيدينا فعليّاً ونفكّ «أيّ خيط» سقط و«لماذا»، أو «ما الذي» يستمرّ في الإمساك بالذاكرة.

في هذا المقال، وكاستكمال لمقال الجمع، نركّز على خطوات قراءة ملفّ التفريغ الذي جُمع فعليّاً بـ WinDbg وامتداد SOS. نتناول التثبيت وضبط الرموز، وتحميل امتداد SOS الضروريّ لتطبيقات .NET، و«ما الذي يُنظر إليه وكيف يُحكم عليه» بأوامر نموذجيّة مثل !clrstack و!dumpheap -stat، وأمر !analyze -v للأعطال الأصليّة، وصولاً إلى التمييز عن dotnet-dump analyze الذي لا يحتاج WinDbg.

ما يفترضه هذا المقال

البند المحتوى
القارئ المستهدَف من لديه ملفّ تفريغ أعطال (.dmp) في متناول اليد، وهو مسؤول تطوير أو صيانة لتطبيق Windows / .NET ويهمّه الآن قراءة محتواه
المعرفة السابقة القدرة على قراءة تتبّع مكدّس C#. لا نفترض خبرة سابقة بـ WinDbg
المقال السابق طريقة جمع التفريغ عولجت في مقال الجمع. هذا المقال يبدأ من «ملفّ التفريغ موجود أصلاً». حتى إن لم تقرأ المقال السابق، يمكنك المتابعة إن صنعت بنفسك ملفّ تفريغ للتمرين بإجراء القسم 2.4
الأدوات المستخدمة WinDbg (الإصدار الحاليّ)، وامتداد SOS، وdotnet-dump عند الحاجة

في ما يلي أربعة اختصارات نستخدمها دون تنبيه لاحق.

الاختصار الاسم الكامل معناه في هذا المقال
WER Windows Error Reporting آليّة الإبلاغ عن الأخطاء القياسيّة في Windows. بإعداد LocalDumps يمكن حفظ ملفّ التفريغ تلقائيّاً عند العطل (خطوات الضبط في مقال الجمع)
PDB Program Database ملفّ الرموز الذي يُولَّد عند البناء. يحمل جدول تطابق أسماء ملفّات المصدر وأرقام الأسطر (الفصل 8)
CLR Common Language Runtime محرّك تنفيذ .NET نفسه. يُحمَّل باسم clr.dll / coreclr.dll1
SOS Son of Strike امتداد مصحّح يقرأ داخل ذلك CLR. نحمّله في الفصل 32

1. الخلاصة أوّلاً

  • الأداة الرئيسة لتحليل ملفّات التفريغ هي WinDbg (الإصدار الحاليّ، المعروف سابقاً بـ WinDbg Preview). يمكن الحصول عليه عبر winget install Microsoft.WinDbg أو من Microsoft Store، ويعمل على معماريّتَي x64 وARM64 في Windows 10 Anniversary Update (1607) فما بعده / Windows 11.3
  • حتى إن لم تُحمَّل الرموز (PDB)، تعمل أوامر مثل !clrstack و!dumpheap -stat و!gcroot كما هي انطلاقاً من بيانات CLR الوصفيّة وبيانات الكومة. ما يُفقَد هو اسم ملفّ مصدر الشيفرة المُدارة ورقم السطر، واسم رمز الإطارات الأصليّة. لكن إن أردت التتبّع حتى سطر المصدر فالأمر مختلف، لذا القاعدة الثابتة هي تمرير كلّ من خادم الرموز العامّ لمايكروسوفت وموقع PDB الخاصّ بالشركة إلى _NT_SYMBOL_PATH.4 خطوات ضبط الرموز في الفصل 2، و«ما الذي يختفي بوجود PDB أو غيابه» والتشغيل في الفصل 8.
  • في ملفّات تفريغ تطبيقات .NET (Framework / Core / 5+)، لا تظهر المعلومات المُدارة إلا بعد تحميل امتداد SOS. لا يمكن تتبّع شيفرة C# بأمر k الأصليّ (عرض المكدّس) وحده.2
  • أنماط التحقيق النموذجيّة ثلاثة. إن كان السبب استثناءً: !clrstack ثم !pe، إن كانت الذاكرة تتزايد باستمرار: !dumpheap -stat ثم !gcroot، إن كان عطلاً أصليّاً: ابدأ بـ !analyze -v.
  • كخيار لا يحتاج WinDbg، توجد dotnet-dump analyze. يمكن استخدام معظم أوامر SOS كما هي، لكنّها لا تتعامل مع إطارات المكدّس الأصليّة. للتحقيق المُدار الخالص الذي لا يتداخل فيه DLL أصليّ أو COM، تثبيت هذه الأداة أخفّ.5
  • الخطوات المشروحة هنا هي «كيفيّة القراءة» وليست «كيفيّة الجمع». راجع مقال الجمع بخصوص طريقة الحصول على ملفّ التفريغ (WER / ProcDump / MiniDumpWriteDump)، وراجع «تصميم يُبقي السجلات والـ dump عند انهيار تطبيق Windows» بخصوص تصميم مطابقتها مع السجلات.

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 19، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. ثبّت WinDbg ومرّر الرموز

2.1 التثبيت

يمكن تثبيت الإصدار الحاليّ من WinDbg بإحدى الطريقتين التاليتين.3

winget install Microsoft.WinDbg

يُثبَّت المحرّك نفسه أيضاً عبر Microsoft Store، والأوامر والامتدادات وسير العمل مشتركة. يُحدَّث تلقائيّاً بعد التثبيت (في الخلفيّة عند التثبيت عبر Store أو المباشر، وعبر winget upgrade Microsoft.WinDbg في حالة winget)، لذا لن تقلقك كثيراً اختلافات السلوك الناتجة عن اختلاف الإصدار.3

2.2 فتح ملفّ التفريغ

windbg -z C:\CrashDumps\MyApp\MyApp_20260702_101500.dmp

-z خيار لتشغيل البرنامج مع تحديد ملفّ التفريغ. للفتح من الواجهة الرسوميّة، استخدم بند فتح التفريغ في قائمة File (في WinDbg Classic يُسمَّى Open crash dump، والاختصار Ctrl+D).6

علماً بأنّ هذا المقال لا يضمّ لقطات شاشة. بدلاً منها نكتب موضع إدخال الأوامر وأسماء القوائم نصّاً. عند فتح ملفّ التفريغ تُفتح نافذة أداة Command،7 وموجّه الإدخال هو سطر مثل 0:000> في أسفلها. كلّ الأوامر المسبوقة بـ ! الواردة لاحقاً تُكتب في نافذة Command هذه (وأمثلة التحليل لدى Microsoft تُكتب أيضاً بالشكل 0:000> !analyze -v).8 الجزء 0:000 من الموجّه يعني «الخيط 0 من العمليّة 0 هو المحدَّد»، وإذا بدّلت الخيط بـ ~5s في الفصل 4 صار 0:005>.

2.3 ضبط مسار الرموز

الموقع الذي يبحث فيه مصحّح Windows عن ملفّات الرموز (PDB) يُحدَّد بمتغيّر البيئة _NT_SYMBOL_PATH، أو بأمر .sympath داخل الجلسة.4 الشكل الأساسيّ في العمل الفعليّ هو تمرير كلّ من خادم الرموز العامّ لمايكروسوفت وموقع PDB الخاصّ بالشركة.

.symfix C:\Symbols\Microsoft
.sympath+ C:\Symbols\MyApp
.reload
  • .symfix اختصار يضبط المسار إلى خادم الرموز العامّ لمايكروسوفت (https://msdl.microsoft.com/download/symbols) مع ذاكرة تخزين مؤقّت محليّة محدَّدة. تُنزَّل رموز DLL القياسيّة لنظام التشغيل تلقائيّاً من هنا.9
  • .sympath+ يضيف موقع PDB الخاصّ بالشركة إلى المسار الحاليّ. يجب توفير PDB الخاصّ بشيفرة الشركة بنفسك، فهو غير موجود على خادم رموز مايكروسوفت.
  • أعد التحميل بـ .reload، وتحقّق من حالة الرموز في قائمة الوحدات (modules).

في حال الضبط الدائم عبر متغيّر البيئة، يكون الشكل كالتالي. هذا الأسلوب مناسب للتحليل التلقائيّ في CI أو خوادم البناء.

set _NT_SYMBOL_PATH=srv*C:\Symbols\Microsoft*https://msdl.microsoft.com/download/symbols;C:\Symbols\MyApp

يمكن التحقّق من قراءة الرموز بشكل صحيح بإخراج قائمة الوحدات عبر أمر lm (loaded modules)، ومعرفة ما إذا كانت الوحدة المستهدَفة قد أصبحت pdb symbols. إن بقيت على حالة deferred، فالرموز لم تُحلّ بعد.

2.4 اصنع بنفسك ملفّ تفريغ للتمرين

التمرين فجأة على تفريغ عطل إنتاجيّ أمر شاقّ، لذا يسرّع الفهم أن تُعدّ تطبيقاً صغيراً يسقط عمداً وتجرّب إجراءات هذا المقال من أوّلها إلى آخرها. إن لم يكن لديك ملفّ تفريغ قابل للقراءة، فابدأ من هنا.

اصنع تطبيقاً واحداً لوحدة التحكّم (.NET 8 / C# 12. استبدل Program.cs في المشروع الذي أنشأته بـ dotnet new console -n CrashLab بالكامل بما يلي).

// Program.cs
using System;
using System.Collections.Generic;

// (A) 第5章の !dumpheap -stat / !gcroot 用: 解放されないまま増えていく配列
var cache = new List<byte[]>();
for (int i = 0; i < 2000; i++)
{
    cache.Add(new byte[100_000]);
}
Console.WriteLine($"cached: {cache.Count} blocks");

// (B) 第4章の !clrstack / !pe 用: 未処理例外でプロセスを落とす
string? name = null;
Console.WriteLine(name!.Length);   // ここで NullReferenceException

بعد ذلك، اضبط ProcDump من Sysinternals على «اكتب تفريغاً كاملاً عند وقوع استثناء غير معالَج» ثم شغّل هذا التطبيق. -ma تفريغ كامل، و-e خيار «اكتب التفريغ عندما تواجه العمليّة استثناءً غير معالَج»، و-x <مجلّد الإخراج> <الملفّ التنفيذيّ> خيار يجعل ProcDump نفسه يشغّل الملفّ التنفيذيّ المحدَّد ويراقبه.10

procdump.exe -accepteula -ma -e -x C:\CrashDumps CrashLab.exe

إن كان الهدف عمليّة تعمل أصلاً، فمرّر اسم العمليّة (أو PID) بالشكل procdump.exe -accepteula -ma -e CrashLab.exe. اسم ملفّ التفريغ الافتراضيّ هو PROCESSNAME_YYMMDD_HHMMSS.dmp.10

إذا قرأت ملفّ .dmp الناتج في WinDbg بإجراء القسم 2.2، أمكنك التمرّن على الفصلين 4 و5 بهذا الملفّ الواحد. إن كان التحقيق المُدار الخالص كافياً، يمكن جمع تفريغ مكافئ أيضاً بـ dotnet-dump collect --name CrashLab (الفصل 7). غير أنّ dotnet-dump collect أداة «تلتقط في توقيت اختياريّ»، لذا إن أردت الإمساك بلحظة السقوط فاستخدم ProcDump أو WER LocalDumps.

3. حمّل امتداد SOS

ملفّات تفريغ تطبيقات .NET لا تُظهر «محتويات الكومة المُدارة» ولا «إطارات مكدّس C#» ولا «محتويات كائن الاستثناء» بأوامر WinDbg الأصليّة وحدها. ما يسدّ هذه الفجوة هو امتداد SOS (Son of Strike). عبر أوامر SOS تجري تحقيق الكومة، وكشف تلف الكومة، وعرض أنواع البيانات الداخليّة لبيئة التشغيل، وفهم حالة الشيفرة المُدارة قيد التنفيذ.2

3.1 الفرق بحسب بيئة التشغيل

يختلف مصدر بيئة التشغيل وSOS الواجب تحميلهما بحسب ما إذا كان التطبيق المستهدَف .NET Framework أو .NET (Core) / .NET 5+.

الهدف جوهر بيئة التشغيل أمر التحميل
.NET Framework clr.dll .loadby sos clr
.NET Core / .NET 5+ coreclr.dll .loadby sos coreclr

.loadby أمر يبحث عن DLL الامتداد (sos.dll) الموجود في المجلّد نفسه الذي توجد فيه الوحدة المحدَّدة (clr أو coreclr) ويحمّله. ميزته أنّه يلتقط بشكل مؤكَّد إصدار SOS المطابق للبيئة التي جُمع منها ملفّ التفريغ، دون الحاجة إلى كتابة المسار الكامل.1

في WinDbg وcdb من الإصدار 10.0.18317.1001 فما بعده، عند اكتشاف أنّ العمليّة المستهدَفة تحمّل coreclr.dll (أو libcoreclr.so في Linux/macOS)، يُحمَّل امتداد .NET تلقائيّاً من Microsoft Extension Gallery.1 وموضع .loadby المذكور أعلاه هو كتابته يدوياً عندما لا ينجح التحميل التلقائيّ، أو عند استخدام إصدار قديم من المصحّح.

3.2 إن لم يُعثر على SOS

في البيئات التي لا يعمل فيها التحميل التلقائيّ، يمكن التثبيت محليّاً بأداة dotnet-sos.

dotnet tool install --global dotnet-sos
dotnet-sos install

بعد التثبيت، يمكن أيضاً تحميله يدوياً في WinDbg كالتالي (قد يلزم هذا في المصحّحات القديمة).11

.load %USERPROFILE%\.dotnet\sos\sos.dll

3.3 تحقّق من نجاح التحميل

!sos.help

أو جرّب !Threads إن كان الهدف من سلسلة Core، أو !sosstatus إن كان من سلسلة Framework، فإن رجعت معلومات دون خطأ فالتحميل ناجح. إن فشل الأمر هنا بخطأ من نوع Unable to find module، فالسبب غالباً هو مسار الرموز أو عدم تطابق بيئة التشغيل (كاختلاف عدد البتّات أو الإصدار بين بيئة جمع ملفّ التفريغ وبيئة التشغيل المحليّة). الحالات التي يُعلَق فيها هنا قبل الوصول إلى أوامر الفصل التالي ليست نادرة في العمل الفعليّ أيضاً.

4. قراءة الاستثناء والمكدّس ── !clrstack و!pe

هذه هي الخطوة الأولى لملفّ تفريغ سقط باستثناء غير معالَج.

!threads

أوّلاً، تحقّق عبر !Threads (اسمها المستعار clrthreads في بيئة lldb) من قائمة الخيوط المُدارة، وعمود Exception الخاصّ بكلّ خيط.12 إن وُجد خيط يحمل استثناءً، انتقل إليه.

المُخرَج جدول سطر لكلّ خيط، ويضمّ الرقم التسلسليّ في المصحّح، ومعرّف خيط CLR، ومعرّف خيط نظام التشغيل، إضافة إلى عمود Domain الذي يبيّن نطاق التطبيق قيد التنفيذ، وعمود APT الذي يبيّن وضع COM apartment، وعمود Exception الذي يبيّن آخر استثناء أُلقي في ذلك الخيط.12 ما يُنظر إليه أوّلاً هو عمود Exception وحده. السطر الذي يحمل فيه اسم نوع مثل System.NullReferenceException هو هدف التحقيق، فاحفظ الرقم التسلسليّ عند الطرف الأيسر من ذلك السطر.

~5s
!clrstack

~<رقم>s أمر ينتقل إلى الخيط ذي الرقم التسلسليّ الذي حفظته. ثم يعرض !CLRStack تتبّع المكدّس للشيفرة المُدارة فقط.13 إن أردت رؤية المعطيات (arguments) والمتغيّرات أيضاً، أضف -a (اختصار يجمع -l و-p).

!clrstack -a

يكون المُخرَج بالشكل التالي (مقتطف من مثال clrstack المنشور في Microsoft Learn. المثال من جلسة dotnet-dump، لكن !clrstack في WinDbg يعرض الشيء نفسه).5

OS Thread Id: 0x573d (0)
    Child SP               IP Call Site
00007FFD28B42C58 00007fb22c1a8ed9 [HelperMethodFrame_PROTECTOBJ: 00007ffd28b42c58] System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean, Boolean)
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.Program.Foo4(System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42ED0 00007FB1B18D2FC4 SymbolTestApp.Program.Foo2(Int32, System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 29]
00007FFD28B42F00 00007FB1B18D2F5A SymbolTestApp.Program.Foo1(Int32, System.String) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 24]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.Program.Main(System.String[]) [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]
00007FFD28B43210 00007fb22aa9cedf [GCFrame: 00007ffd28b43210]

أيّ الأسطر تُقرأ كالتالي.

  1. اقرأ عمود Call Site وحده، من الأسفل إلى الأعلى. الأسفل هو جهة الاستدعاء، والأعلى هو المستدعى. في هذا المثال تقرأ المسار Main ثم Foo1 ثم Foo2 ثم Foo4 حتى موضع السقوط.
  2. الأقواس المربّعة [... .cs @ 54] هي اسم ملفّ المصدر ورقم السطر. إن ظهرت فقد حُلّت الرموز. إن لم تظهر فالعلاج في الفصل 8.
  3. تخطَّ أسطر [HelperMethodFrame_...] و[GCFrame: ...]. هذه إطارات داخليّة لبيئة التشغيل، ولا تقابل علّة في شيفرة الشركة مباشرة.
  4. عمودا Child SP وIP لا يُقرآن في التحقيق العاديّ. هما مؤشّر المكدّس ومؤشّر التعليمات، ولا يُستخدمان إلا عند تمرير عنوان إلى أوامر مثل !dumpstackobjects.
  • يعدّد CLRStack الإطارات المُدارة مباشرة من بيانات CLR الوصفيّة، لذا وجود الرموز أو غيابها لا يؤثّر في ظهور الإطار من عدمه. ما يختفي عند نقص PDB هو اسم ملفّ المصدر ورقم السطر المذكوران في النقطة 2 أعلاه فقط، ولا يُحذف الإطار نفسه أبداً.13 جمعنا حديث الرموز في الفصل 8، فارجع إليه عندما لا يظهر رقم السطر.
  • إن لم يظهر ولا إطار واحد من شيفرة الشركة، فالمشتبه به ليس نقص الرموز، بل الآتي: الخيط المحدَّد خيط آخر لا يحمل الاستثناء (خطأ في اختيار الخيط)، أو أنّ السقوط حدث في الجانب الأصليّ فقط ولا توجد إطارات مُدارة أصلاً، أو أنّ نوع ملفّ التفريغ (مثل Mini) لا يتضمّن معلومات المكدّس في تلك اللحظة بشكل كافٍ.

بعد ذلك انظر إلى كائن الاستثناء نفسه.

!pe

يعرض !PrintException (اختصاره !pe)، إن لم تحدّد عنواناً، آخر استثناء أُلقي في الخيط الحاليّ. يمكن الحصول على اسم النوع، والرسالة، والاستثناء الداخلي (يُعرض بـ -nested)، وحتى نصّ تتبّع المكدّس.14 يكون المُخرَج بالشكل التالي (هذا أيضاً مقتطف من مثال منشور في Microsoft Learn، مع -lines لإظهار معلومات المصدر).5

Exception object: 00007fb18c038590
Exception type:   System.Reflection.TargetInvocationException
Message:          Exception has been thrown by the target of an invocation.
InnerException:   System.Exception, Use !PrintException 00007FB18C038368 to see more.
StackTrace (generated):
SP               IP               Function
00007FFD28B42E20 00007FB1B18D33ED SymbolTestApp.dll!SymbolTestApp.Program.Foo4(System.String)+0x15d [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 54]
00007FFD28B42F30 00007FB1B18D168E SymbolTestApp.dll!SymbolTestApp.Program.Main(System.String[])+0x6e [/home/mikem/builds/SymbolTestApp/SymbolTestApp/SymbolTestApp.cs @ 19]

StackTraceString: <none>
HResult: 80131604

ما يُقرأ هو الأسطر الأربعة العليا. Exception type يبيّن نوع الاستثناء، وMessage شرحه، وInnerException هو «السبب الحقيقيّ». عندما يظهر TargetInvocationException كما في هذا المثال، فالنوع الظاهر غلاف لاستدعاء بالانعكاس (reflection) فحسب، لذا مرّر العنوان المكتوب في سطر InnerException بالشكل !pe 00007FB18C038368 وافتح الاستثناء الداخلي (!pe -nested يعطي المعلومات نفسها).14 HResult قيمة HRESULT المقابلة للاستثناء، وما بعد StackTrace (generated) هو تتبّع المكدّس الذي يحمله الاستثناء، والفرق أنّ !clrstack هو «المكدّس الآن»، بينما هذا هو «المكدّس لحظة إلقاء الاستثناء». إن اختلف الاثنان، اشتبه في أنّ الاستثناء أُمسك ثم أُعيد إلقاؤه في موضع آخر.

كلّما كان الاستثناء لا يخبر بشيء من اسم نوعه وحده، مثل System.NullReferenceException، ازدادت الحاجة إلى مطابقته مع قيم المتغيّرات المحليّة الظاهرة عبر !clrstack -a.

5. تتبّع الكومة والتسريب ── !dumpheap -stat و!gcroot

هذه هي الأوامر المحوريّة في تحقيقات من نوع «الذاكرة تتزايد تدريجيّاً ويسقط التطبيق بعد ساعات إلى أيّام». كتبنا بالتفصيل عن المرحلة السابقة، وهي التمييز بين انتظار GC والتسريب الحقيقيّ، في «التمييز بين انتظار GC وتسريب الذاكرة في .NET». هذا المقال استكمال لذلك، ويقابل الجزء الذي يتعمّق حتى «ما الذي يمسك بها» بقراءة ملفّ تفريغ واحد.

!dumpheap -stat

يعرض خيار -stat ملخّصاً إحصائيّاً فقط للكومة المُدارة. تُصفّ الأنواع بشكل قريب من الترتيب التنازليّ حسب العدد والحجم الإجماليّ، فنحدّد أوّلاً «النوع الذي يهيمن بالكمّ».15 يكون المُخرَج بالشكل التالي (مُخرَج فعليّ منشور في درس تحقيق تسريب الذاكرة في Microsoft Learn).15

Statistics:
              MT    Count    TotalSize Class Name
00007f6c1eeefba8      576        59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8     1749        95696 System.SByte[]
00000000008c9db0     3847       116080      Free
00007f6c1e784a18      175       128640 System.Char[]
00007f6c1dbf5510      217       133504 System.Object[]
00007f6c1dc014c0      467       416464 System.Byte[]
00007f6c21625038        6      4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498   200000      4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90   206770     19494060 System.String
Total 428516 objects

أيّ الأسطر تُقرأ كالتالي.

  1. اقرأ من الأسفل. الترتيب تصاعديّ بحسب TotalSize (بالبايت)، لذا السطر الأخير هو النوع الذي يأكل الكومة أكثر من غيره. في هذا المثال يشغل System.String نحو 19MB، وCustomer نحو 4.8MB.
  2. افصل النظر إلى عمود Count عن عمود TotalSize. موضع الشكّ يختلف بحسب ما إذا كانت مصفوفات ضخمة قليلة العدد (الحجم وحده كبير) أو كائنات صغيرة بمئات الآلاف (العدد وحده كبير).
  3. سطر Free ليس تسريباً. هو فجوات جُمعت ولم يُعد استخدامها بعد، ويُنظر إليه مؤشّراً على التجزئة.
  4. احفظ عمود MT (عنوان MethodTable). إن مرّرته إلى الخطوة التالية !dumpheap -mt <MT> خرجت قائمة عناوين النسخ الفرديّة لذلك النوع.

السلسلتان اللتان تُشاهدان كثيراً في العمل الفعليّ هما التاليتان.

  • فئة الأعمال نفسها تتزايد (مثلاً: مئات الآلاف من MyApp.Models.Customer) ── يوجد مرجع قويّ (strong reference) يستمرّ في الإمساك بها في مكان ما
  • System.String أو المصفوفات وحدها مفرطة الكثرة ── غالباً ما يكون السبب البيانات الداخليّة لفئة الأعمال الظاهرة في الأعلى، والأسرع غالباً الاشتباه بجانب فئة الأعمال أوّلاً قبل النظر إلى كلّ نسخة على حدة

بعد تضييق النطاق، خذ عنوان النسخة الفرديّة.

!dumpheap -type MyApp.Models.Customer

-type مطابقة جزئيّة لاسم النوع، و-mt تحديد عنوان MethodTable الذي حفظته في المرحلة السابقة. في الحالتين يكون المُخرَج قائمة «سطر لكلّ نسخة».15

         Address               MT     Size
00007f6ad09421f8 00007f6c20a67498       24
00007f6ad0942210 00007f6c20a67498       24

ما يُستخدم هنا هو عمود Address الأيسر وحده. مرّر هذه القيمة إلى الأمر التالي.

ثم ابحث عن سبب بقاء ذلك الكائن دون أن يجمعه GC.

!gcroot 000001a2b3c4d5e0

يبحث !GCRoot في الكومة المُدارة وجدول المقابض (handle table) بأكملهما، ويكشف الجذور (roots) التي تصل إلى الكائن المحدَّد (مثل المتغيّرات على المكدّس، والحقول الساكنة، ومقابض GC وغيرها).16 يكون المُخرَج بالشكل التالي (هذا أيضاً مُخرَج فعليّ منشور في Microsoft Learn).15

Thread 3f68:
    00007F6795BB58A0 00007F6C1D7D0745 testwebapi.Controllers.CustomerCache.GetAll()
        rbx:  (interior)
            ->  00007F6BDFFFF038 System.Object[]
            ->  00007F69D0033570 testwebapi.Controllers.Processor
            ->  00007F69D0033588 testwebapi.Controllers.CustomerCache
            ->  00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer]]
            ->  00007F6C000148A0 testwebapi.Controllers.Customer[]
            ->  00007F6AD0942258 testwebapi.Controllers.Customer

Found 1 root.

أيّ الأسطر تُقرأ كالتالي.

  1. سطر العنوان الأوّل هو نوع الجذر. Thread 3f68: يعني متغيّراً على مكدّس الخيط، وHandleTable: يعني مقبض GC (مع ذكر النوع مثل (pinned handle)). إن كان الجذر حقلاً ساكناً، ظهر هنا اسم النوع الحامل لذلك الحقل.
  2. اقرأ سلسلة -> من الأعلى إلى الأسفل. هي سلسلة «من يمسك بمن»، ونهايتها الكائن المحدَّد.
  3. الجاني ليس النهاية، بل مجموعة أو كائن طويل العمر يظهر في وسط السلسلة. في هذا المثال يتّضح أنّ CustomerCache ما زال يمسك List<Customer>. ما لم تُصلح هذه النقطة، فلن يحلّ النظر إلى Customer النهائيّ وحده شيئاً.
  4. أكّد العدد في السطر الأخير Found N root.. إن ظهرت جذور متعدّدة، لن يُجمع الكائن ما لم تُقطع كلّها. لا تتوقف عند إصلاح واحدة.

إن ظهر في المُخرَج حقل ساكن للتخزين المؤقّت أو اشتراك في معالِج حدث، فذلك مرشّح للسبب المتمثّل في نسيان الإلغاء. الشيفرة التالية، من نوع «إبقاء العنصر في التخزين المؤقّت دون تحريره»، مثال نموذجيّ.

public static class CustomerCache
{
    // 解除する経路がなく、増え続ける一方の静的辞書
    private static readonly Dictionary<int, Customer> _cache = new();

    public static void Add(Customer c) => _cache[c.Id] = c;
}

إذا ظهر في مُخرَج !gcroot حاوٍ ساكن مثل CustomerCache، فذلك مادّة لدراسة إضافة مدّة صلاحيّة أو حدّ أقصى لعدد العناصر في جانب الشيفرة، أو التحوّل إلى WeakReference.15

6. التحليل التلقائيّ للعطل الأصليّ ── !analyze -v

في الأعطال الأصليّة (مثل انتهاك الوصول) التي يتداخل فيها DLL بلغة C++ أو COM أو SDK من جهة مورّد، ابدأ بهذا الأمر أوّلاً.

!analyze -v

!analyze أمر امتداد يجري تحليلاً تلقائيّاً للعطل والاستثناء، ويصبح العرض تفصيليّاً بإضافة -v.8 يمتدّ المُخرَج عشرات الأسطر، لكن إن اقتطفنا الحقول الرئيسة من مثال تحليل وضع المستخدم في Microsoft Learn صار كالتالي.8

FAULTING_IP:
ntdll!PropertyLengthAsVariant+73
77f97704 cc               int     3

EXCEPTION_RECORD:  ffffffff -- (.exr ffffffffffffffff)
ExceptionAddress: 77f97704 (ntdll!PropertyLengthAsVariant+0x00000073)
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000

BUGCHECK_STR:  80000003

PROCESS_NAME:  MyApp.exe

STACK_TEXT:
0006b9dc 01050963 00000000 0006ba04 000603fd ntdll!PropertyLengthAsVariant+0x73
0006b9f0 010509af 00000002 0006ba04 77e1a449 MyApp!FatalErrorBox+0x55 [D:\source_files\MyApp\util.c @ 541]
0006da04 01029f4e 01069850 0000034f 01069828 MyApp!ShowAssert+0x47 [D:\source_files\MyApp\util.c @ 579]
0006ff70 01062cbf 00000001 00683ed8 00682b88 MyApp!main+0x1e6 [D:\source_files\MyApp\MyApp.c @ 263]

FOLLOWUP_IP:
MyApp!FatalErrorBox+55
01050963 5e               pop     esi

SYMBOL_NAME:  MyApp!FatalErrorBox+55

MODULE_NAME:  MyApp

IMAGE_NAME:  MyApp.exe

العناصر الثلاثة التي ينبغي النظر إليها خصوصاً في المُخرَج هي التالية.

  • EXCEPTION_CODE / BUGCHECK_STR: نوع الشذوذ (انتهاك وصول، فيضان مكدّس، وغيرهما). في المثال أعلاه 80000003 (استثناء تعليمة توقّف)، ويُرفق سطر ExceptionCode في EXCEPTION_RECORD بشرح موجَّه للقارئ. في تفريغ وضع المستخدم يدخل رمز الاستثناء هذا أيضاً في BUGCHECK_STR (الاسم موروث من النواة وقد يضلّل، لكنّه ليس bugcheck = شاشة زرقاء).8
  • FAULTING_IP / FOLLOWUP_IP: عنوان التعليمة التي سقطت فعليّاً، واسم الوحدة والدالّة المقابلين. FAULTING_IP هو «موضع السقوط»، وFOLLOWUP_IP هو الموضع الذي قدّر !analyze أنّ «السبب هنا على الأرجح»، وغالباً لا يتطابقان. في المثال أعلاه السقوط في ntdll، بينما السبب المقدَّر هو MyApp!FatalErrorBox الخاصّ بالشركة.
  • MODULE_NAME / IMAGE_NAME: هل موضع السقوط وحدة خاصّة بالشركة أم DLL تابع لجهة خارجيّة. مع SYMBOL_NAME يُكتب هنا الوحدة التي ينتمي إليها FOLLOWUP_IP.

في STACK_TEXT السطر الأوّل هو الأعمق (موضع السقوط)، وكلّما نزلت صرت أقرب إلى جهة الاستدعاء. أقصر قراءة هي أن تبحث من الأعلى عن أوّل سطر يظهر فيه اسم وحدة الشركة (في المثال أعلاه MyApp!) وتنظر إلى [اسم الملفّ @ رقم السطر] في ذلك السطر.

إن كان السقوط داخل DLL تابع لمورّد وليس وحدة خاصّة بالشركة، فتتبّع ما بعد تلك النقطة يحتاج إلى PDB الخاصّ بالمورّد (وغالباً لا يمكن الحصول عليه). النتيجة الواقعيّة في العمل الفعليّ هي الرجوع إلى جهة الاستدعاء (آخر معطيات مرّرتها شيفرة الشركة) والاشتباه في «هل كانت القيمة الممرَّرة غير صحيحة». تحقّق عبر حقل STACK_TEXT في !analyze -v ممّا استدعته شيفرة الشركة مباشرة قبل السقوط.

يمكن استخدام !analyze أيضاً في غير ملفّات التفريغ التي وقع فيها استثناء. عند الاشتباه بتجمّد (hang)، بعد اختيار الخيط المستهدَف ينفّذ الأمر التالي تحليلاً لعلاقات الحظر (blocking) بين الخيوط.

!analyze -hang

7. خيار لا يستخدم WinDbg ── dotnet-dump analyze

إن أردت فحص الشيفرة المُدارة فقط لـ .NET Core / .NET 5+ (دون تداخل DLL أصليّ أو COM)، فإنّ dotnet-dump الأخفّ من WinDbg خيار أيضاً.

dotnet tool install --global dotnet-dump
dotnet-dump analyze C:\CrashDumps\MyApp\MyApp_20260702_101500.dmp

يفتح الأمر الفرعيّ analyze جلسة تفاعليّة مُثبَّت فيها SOS مسبقاً، ويمكن استخدام معظم الأوامر المذكورة حتى الآن مثل clrstack وdumpheap وgcroot كما هي دون بادئة !.5

معيار التمييز بين الاستخدامين كالتالي.

المعيار WinDbg + SOS dotnet-dump analyze
إطارات المكدّس الأصليّة تظهر لا تظهر (مُدارة فقط)5
التحليل التلقائيّ الأصليّ عبر !analyze -v متاح غير متاح
ملفّات تفريغ Linux يمكن تحليلها بـ WinDbg على Windows (استخدام إصدار x64 لملفّات x64، وx64 لملفّات Arm64، وx86 لملفّات x86)17 مدعوم (استخدام أداة بعدد بتّات المنصّة نفسها)17
ملفّات تفريغ macOS غير مدعوم (دعم WinDbg لملفّات تفريغ Linux لا يشمل macOS) مدعوم (.NET 5 فما بعده)5
خفّة الاعتماد مثبّت أو winget أمر واحد عبر dotnet global tool
الدمج في CI متعدّد المنصّات يتطلّب جهداً سهل

الخطّ الفاصل في العمل الفعليّ هو: WinDbg إن كان «يُحتمل تداخل COM أو P/Invoke أو DLL أصليّ»، وdotnet-dump analyze إن كان «تحقيقاً في تسريب ذاكرة لشيفرة مُدارة خالصة، مع الرغبة في التشغيل على CI أو عدّة منصّات». بما أنّ الأداتين تشتركان في نظام أوامر SOS، فإنّ الأوامر التي تُتعلَّم في إحداهما تعمل تقريباً كما هي في الأخرى. انتبه إلى أنّه عند التعامل مع ملفّ تفريغ ملتقَط على macOS لا يكون WinDbg خياراً متاحاً، فيصبح dotnet-dump (أو LLDB) هو الخيار الوحيد.

8. إن لم تُقرأ الرموز فلن يبدأ شيء

نجمع حديث الرموز (PDB) في هذا الفصل. في الفصول السابقة اكتفينا بالقول «إن وُجد PDB ظهر رقم السطر»، ومحتوى ذلك يكفي أن يُقرأ هنا وحده.

أوّلاً، بعض الخطوات المذكورة حتى الآن تعمل بشكل معقول حتى إن لم تُحمَّل PDB بشكل صحيح. تقرأ !clrstack و!dumpheap -stat و!gcroot بيانات CLR الوصفيّة وبيانات الكومة مباشرة، لذا تُعرض الإطارات ومعلومات الأنواع نفسها حتى بلا PDB. ما يُفقَد بغياب PDB هو اسم ملفّ مصدر الشيفرة المُدارة ورقم السطر، واسم رمز الإطارات والوحدات الأصليّة (يُعرض العنوان فقط بدل اسم الدالّة).

ما تريد رؤيته بلا PDB مع PDB
إطار المكدّس المُدار (اسم الدالّة العضويّة ونوع المعطيات) يظهر يظهر
اسم ملفّ المصدر ورقم السطر المُداران (!clrstack) لا يظهر يظهر13
نوع الاستثناء ورسالته والاستثناء الداخلي (!pe) يظهر يظهر
اسم النوع والعدد والحجم على الكومة (!dumpheap -stat) يظهر يظهر
مسار المرجع إلى جذر GC (!gcroot) يظهر يظهر
اسم دالّة الإطار الأصليّ (k / STACK_TEXT في !analyze -v) لا يظهر (العنوان فقط) يظهر

أي أنّه في المواقف التي لا يتوفّر فيها سوى ملفّ التفريغ والملفّ التنفيذيّ، وتريد فيها فقط «فهم ما حدث في البداية»، لا حرج في البدء بـ !threads ثم !clrstack قبل التعثّر في البحث عن PDB. لكن إن أردت التتبّع حتى سطر المصدر لتحديد موضع السبب، فالأمر مختلف. أضفنا في الفصل 2 مسار PDB الخاصّ بالشركة إلى .sympath+، لكن في العمل الفعليّ يحدث كثيراً أنّ مجرّد «عدم معرفة أين يوجد PDB المقابل لملفّ EXE/DLL الموزَّع» يوقف التحقيق دون الوصول إلى تحديد سطر المصدر، وذلك أكثر تواتراً ممّا كُتب في مقال الجمع.

بخصوص ما يحمله PDB نفسه وما لا يحمله، وPortable PDB، وSource Link (آليّة تضمّن بيانات إدارة المصدر الوصفيّة داخل التجميعة (assembly) وتتيح للمصحّح جلب المصدر عند نقطة الالتزام (commit) المطابقة مباشرة)، جمعناها كلّها في مقال واحد بعنوان «PDB (قاعدة بيانات البرنامج) ما هي؟».18 إذا أردت دمج تحليل ملفّات التفريغ في تشغيل مستمرّ، فإنّ حفظ PDB لكلّ عمليّة بناء وتفعيل Source Link أمر تحضيريّ لا يقلّ أهمّيّة عن إعداد الجمع نفسه. إهمال هذا يجعلك تتحسّس طريقك مستعيناً بالعناوين وأسماء الأنواع فقط دون أيّ ظهور لسطر المصدر في مُخرَج !clrstack.

9. مثال عمليّ ── تحليل التفريغ في تحقيق تسريب المقابض

المقال الذي كتبناه سابقاً «تحقيق تعطّل الكاميرا الصناعيّة بعد تشغيل طويل: فصل handle leak» هو حالة تحقيق في تطبيق تحكّم بكاميرا صناعيّة يسقط فجأة بعد تشغيل طويل، وكان الجاني الرئيسيّ تسريب مقابض (handle leak) لا تسريب ذاكرة. يفيد ملفّ التفريغ في هذا النوع من التحقيق عند اجتماع العناصر التالية.

  1. التأكّد عبر !dumpheap -stat من أنّ جانب الكومة المُدارة سليم (عدد النسخ وحجمها لكلّ نوع لا يتزايدان باستمرار)
  2. إن كان عدد مقابض العمليّة وحده هو المتزايد رغم ذلك، يمكن الفصل بأنّ المُسرَّب ليس كائناً مُداراً بل مقبض نظام تشغيل (OS handle) (مثل الملفّات والأحداث والمقابض التي يحجزها SDK الكاميرا داخليّاً)
  3. إن بقي كائن غلاف (wrapper) مُدار يحمل SafeHandle، تتبّع جذر GC لذلك الغلاف عبر !gcroot، وحدّد «الموضع الذي بقي فيه مرجع رغم أنّه كان ينبغي تحريره»

بعبارة أخرى، يعمل !dumpheap -stat نقطة تفرّع للحكم على «هل الزيادة في الكومة المُدارة أم لا»، وحالما يتّضح أنّها ليست كذلك ينتقل محور التحقيق إلى أدوات كشف الشذوذ عند الحدود الأصليّة مثل Application Verifier. تناولنا كيفيّة بناء أساس اختبار الحالات الشاذّة هذا في «بنية اختبار مسارات الفشل في Windows بـ Application Verifier». تقسيم الأدوار هو أنّ تحليل ملفّ التفريغ يكشف «الحالة الحاصلة الآن»، بينما يجعل Application Verifier «الشذوذ يتكرّر مسبقاً»، والاستخدام المشترك بينهما هو القاعدة الثابتة في تحقيقات أعطال الأنظمة العاملة لفترات طويلة.

10. الخلاصة

«قراءة» ملفّ تفريغ الأعطال تحتاج وقتاً أطول للإتقان من «جمعه»، لكنّ الأنماط ليست كثيرة.

  1. ثبّت WinDbg ومرّر كلّاً من خادم الرموز العامّ لمايكروسوفت وPDB الخاصّ بالشركة إلى مسار الرموز (الفصل 2)
  2. حمّل امتداد SOS إن كان تطبيق .NET (.loadby sos clr / .loadby sos coreclr، أو التحميل التلقائيّ. الفصل 3)
  3. ابدأ بـ !clrstack ثم !pe إن كان السبب استثناءً، و!dumpheap -stat ثم !gcroot إن كانت الذاكرة تتزايد، و!analyze -v إن كان عطلاً أصليّاً (الفصول 4 إلى 6)
  4. إن كان الاستخدام تحقيقاً مُداراً خالصاً، ادرُس أيضاً dotnet-dump analyze الأخفّ (الفصل 7)

وأساس كلّ هذا هو إدارة PDB والرموز. إذا قرّرت مع إعداد جمع ملفّات التفريغ حفظ PDB لكلّ عمليّة بناء وتفعيل Source Link في الوقت نفسه، فسيتغيّر وقت التحقيق كثيراً عند وقوع عطل فعليّ. إن كان التحليل الداخليّ صعباً أو لا يتوفّر له وقت، يمكننا نحن إجراء التحليل إن أرسلت لنا مجموعة ملفّات التفريغ والسجلات.

مقالات ذات صلة

مجالات الاستشارة ذات الصلة

تتعامل شركة كومورا سوفت ذ.م.م. مع تحقيق أسباب الأعطال بالجمع بين ملفّات تفريغ الأعطال والسجلات، وفصل الأعطال التي لا تظهر إلا بعد تشغيل طويل، والاستشارة حول تصميم نظام حفظ وتحليل ملفّات التفريغ وPDB نفسه.

روابط مرجعية

  1. Microsoft Learn, Debugging Managed Code Using the Windows Debugger. حول أنّ بيئة تشغيل .NET Framework هي clr.dll، وبيئة تشغيل .NET Core/.NET 5+ هي coreclr.dll، وتحميل الامتداد من مجاورة الوحدة عبر .loadby، والتحميل التلقائيّ في WinDbg من الإصدار 10.0.18317.1001 فما بعده.  2 3

  2. Microsoft Learn, SOS debugging extension. حول إمكان استخدام امتداد SOS لجمع معلومات الكومة المُدارة، وكشف تلف الكومة، وعرض أنواع البيانات الداخليّة لبيئة التشغيل، وأنّ الصيغة في WinDbg هي ![command] 2 3

  3. Microsoft Learn, Install the Windows debugger. حول طريقة تثبيت WinDbg عبر winget / Microsoft Store، وأنظمة التشغيل المدعومة (Windows 10 1607 فما بعده وWindows 11) والمعماريّات (x64 وARM64)، وسلوك التحديث التلقائيّ.  2 3

  4. Microsoft Learn, Symbol path for Windows debuggers. حول طريقة ضبط مسار الرموز عبر متغيّر البيئة _NT_SYMBOL_PATH، وإمكان ضبط المسار الافتراضيّ إلى خادم الرموز العامّ عبر أمر .symfix 2

  5. Microsoft Learn, Dump collection and analysis utility (dotnet-dump). حول أنّ dotnet-dump analyze يوفّر جلسة تفاعليّة يمكن فيها استخدام أوامر SOS كما هي، لكن بما أنّه ليس مصحّح أخطاء أصليّاً فلا يمكنه عرض إطارات المكدّس الأصليّة، وأنّ دعم macOS يبدأ من .NET 5.  2 3 4 5 6

  6. Microsoft Learn, Open a Dump File with WinDbg. حول فتح التفريغ من Open crash dump (Ctrl+D) في قائمة File، وخيارات سطر الأوامر -y (مسار الرموز) و-i (مسار الصور) و-z (ملفّ التفريغ). 

  7. Microsoft Learn, Windows Debugger WinDbg Overview. حول تكوين نوافذ أدوات WinDbg مثل Command وSource code وDisassembly وBreakpoints، وضبط الاتّصال والتغييرات من قائمة File

  8. Microsoft Learn, Using the !analyze Extension و!analyze (WinDbg). حول التحليل التلقائيّ للعطل والاستثناء عبر !analyze -v، ومعنى الحقول الواردة في المُخرَج مثل FAULTING_IP وMODULE_NAME، وأمر !analyze -hang الموجَّه لتحقيق التجمّد (hang).  2 3 4

  9. Microsoft Learn, Microsoft public symbol server. حول صيغة مسار الرموز srv*DownstreamStore*https://msdl.microsoft.com/download/symbols، والضبط مع ذاكرة تخزين مؤقّت محليّة عبر .symfix

  10. Microsoft Learn, ProcDump - Sysinternals. حول خيارات -ma (تفريغ كامل) و-e (كتابة التفريغ عند استثناء غير معالَج) و-x <Dump_Folder> <Image_File> (تشغيل الهدف ومراقبته)، واسم ملفّ التفريغ الافتراضيّ PROCESSNAME_YYMMDD_HHMMSS.dmp 2

  11. Microsoft Learn, SOS installer (dotnet-sos). حول تثبيت امتداد SOS محليّاً عبر dotnet-sos install، وأمر التحميل اليدويّ في إصدارات المصحّح القديمة. 

  12. Microsoft Learn, SOS debugging extension - Commands. حول أنّ أمر Threads (اسمه المستعار clrthreads في بيئة lldb) يعرض قائمة تضمّ معرّف كلّ خيط ونطاقه وآخر استثناء أُلقي فيه وغير ذلك.  2

  13. Microsoft Learn, SOS debugging extension - Commands. حول أنّ أمر CLRStack يعرض تتبّع المكدّس للشيفرة المُدارة فقط، وأنّ خيار -a يعرض المتغيّرات المحليّة والمعطيات معاً، وأنّ الرموز (SYMOPT_LOAD_LINES) تؤثّر فقط في إمكان عرض اسم ملفّ المصدر ورقم السطر، ولا علاقة لها بعرض الإطار نفسه.  2 3

  14. Microsoft Learn, SOS debugging extension - Commands. حول أنّ أمر PrintException (اختصاره pe) يعرض عند حذف العنوان آخر استثناء أُلقي في الخيط الحاليّ، وأنّه يمكن عرض الاستثناءات المتداخلة أيضاً عبر -nested 2

  15. Microsoft Learn, Debug a memory leak in .NET وDump collection and analysis utility (dotnet-dump) - Analyze memory leaks and allocations. حول عرض dumpheap -stat للإحصاء المجمَّع حسب النوع (العدد والحجم الإجماليّ)، وطريقة تقدّم التحقيق انطلاقاً منه.  2 3 4 5

  16. Microsoft Learn, SOS debugging extension - Commands. حول أنّ أمر GCRoot يبحث في الكومة المُدارة وجدول المقابض بأكملهما، ويكشف المراجع (الجذور) إلى الكائن المحدَّد. 

  17. Microsoft Learn, Debug Linux dumps. حول إمكان تحليل ملفّات تفريغ Linux على Windows بـ WinDbg أو dotnet-dump، وضرورة استخدام إصدار الأداة المطابق لعدد بتّات بيئة الجمع (x64/Arm64/x86).  2

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

بأيّ أمر أبدأ قراءة ملفّ تفريغ أعطال تطبيق .NET؟
أنماط التحقيق ثلاثة. إن سقط التطبيق باستثناء غير معالَج، حدّد الخيط (thread) الحامل للاستثناء بـ !threads، ثم اعرض المكدّس المُدار بـ !clrstack، وتحقّق من نوع كائن الاستثناء ورسالته والاستثناء الداخلي بـ !pe. إن كان التحقيق في تزايد مستمرّ للذاكرة، حدّد الأنواع ذات الكمّ الكبير بـ !dumpheap -stat، ثم اكشِف بـ !gcroot الجذور (roots) التي تمسك بتلك الكائنات (مثل الحقول الساكنة أو اشتراكات معالِجات الأحداث). أمّا العطل الأصليّ (native) فابدأ فيه بالتحليل التلقائيّ عبر !analyze -v.
كيف أحمّل امتداد SOS في WinDbg؟
بالنسبة إلى .NET Framework الأمر هو .loadby sos clr، وبالنسبة إلى .NET Core/.NET 5+ هو .loadby sos coreclr. يقوم WinDbg من الإصدار 10.0.18317.1001 فما بعده بتحميل SOS تلقائيّاً عندما يكتشف أنّ العمليّة المستهدَفة تحمّل coreclr.dll. وفي البيئات التي لا يعمل فيها التحميل التلقائيّ، يمكن تثبيته محليّاً بأداة dotnet-sos ثم تحميله يدوياً بـ .load. يُتحقَّق من نجاح التحميل بمعرفة ما إذا كانت !sos.help أو !Threads تُرجع معلومات دون خطأ.
هل يمكن تحليل ملفّ التفريغ بلا PDB (رموز)؟
يمكن ذلك إلى حدّ ما. تقرأ !clrstack و!dumpheap -stat و!gcroot بيانات CLR الوصفيّة (metadata) وبيانات الكومة (heap) مباشرة، لذا تُعرض الإطارات (frames) ومعلومات الأنواع حتى بلا PDB. ما يُفقَد هو اسم ملفّ المصدر ورقم السطر في الشيفرة المُدارة، واسم رمز الإطارات الأصليّة (native). إن أردت التتبّع حتى سطر المصدر لتحديد موضع السبب، يلزم تمرير كلّ من خادم الرموز العامّ لمايكروسوفت وموقع PDB الخاصّ بالشركة إلى مسار الرموز. حفظ PDB لكلّ عمليّة بناء (build) وتفعيل Source Link أمران مهمّان تشغيليّاً.
كيف أميّز استخدام dotnet-dump analyze عن WinDbg؟
المعيار هو: WinDbg إن كان يُحتمل تدخّل COM أو P/Invoke أو DLL أصليّ، وdotnet-dump analyze إن كان التحقيق في شيفرة مُدارة خالصة. يمكن تثبيت dotnet-dump بأمر واحد عبر dotnet global tool، ويمكن استخدام معظم أوامر SOS مثل clrstack وdumpheap كما هي، لكنّه لا يتعامل مع إطارات المكدّس الأصليّة ولا يمكن استخدام !analyze -v فيه. ملفّات التفريغ الملتقَطة على macOS لا يمكن تحليلها بـ WinDbg، لذا يكون الخيار الوحيد dotnet-dump أو LLDB. بما أنّ الأداتين تشتركان في نظام أوامر SOS، فإنّ الأوامر التي تتعلّمها في إحداهما تعمل تقريباً في الأخرى أيضاً.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة