Time Travel Debugging ── تسجيل الأخطاء التي لا تتكرّر في التطبيقات طويلة التشغيل وإرجاعها
· آخر تحديث: · غو كومورا · WinDbg, Time Travel Debugging, التصحيح, التحقيق في الأعطال, Windows, تطوير Windows, .NET, C++, الاستشارة التقنيّة
سجل التعديلات (النسخة الأولى، نُشرت في 2 Sep، 2026)
- النشر الأول
«ينهار مرّة في الشهر، في منتصف الليل فقط.» «العمليّة نفسها لا تتكرّر على جهازي أبداً.» «حصلنا على تفريغ، لكن النظر إلى موقع الانهيار لا يخبرنا لماذا صار القيمة ما هي.» من بين تحقيقات أخطاء تطبيقات Windows طويلة التشغيل، هذه الحالات تستهلك أكثر الوقت. في مقال سابق، «قراءة ملفّات تفريغ الأعطال بـ WinDbg وSOS»، نظرنا في كيفيّة قراءة تفريغ انهيار، وهو صورة واحدة. يواصل هذا المقال من هناك ويعالج الأداة للحالات التي لا تكفي فيها الصورة: Time Travel Debugging (TTD).
TTD ميزة في WinDbg تسجّل تنفيذ العمليّة كاملاً وتتيح إعادة تشغيله لاحقاً أماماً وخلفاً. بدل المحاولة مرّة بعد مرّة لإعادة إنتاج خطأ، يمكنك «إرجاع» جلسة المصحّح.1 القرّاء المقصودون مطوّرو تطبيقات Windows و.NET وC++ وصائنوها ممّن أنهوا نصيبهم من تحقيق التفريغ والسجلّ وما زالت لديهم حالات لا يصلون إلى سببها الجذري. بيئة المتطلّبات Windows 10/11 أو Windows Server 2016 فما بعد مع WinDbg الحالي، ويلزم التسجيل امتياز المسؤول.12 الصعوبة متوسّطة.
افتراضات هذا المقال
| البند | المحتوى |
|---|---|
| القرّاء المقصودون | مطوّرو تطبيقات Windows وصائنوها ذوو أخطاء طويلة التشغيل أو متقطّعة لا تصل إليها التفريغات والسجلّات |
| المعرفة المسبقة | خبرة فتح تفريغ في WinDbg وتشغيل !analyze -v أو !clrstack. يُفترض محتوى مقال تحليل SOS |
| بيئة المتطلّبات | Windows 10/11 أو Windows Server 2016/2019/2022/2025، WinDbg (الإصدار الحالي)، TTD.exe، امتياز المسؤول2 |
| خارج النطاق | التكامل مع Visual Studio Enterprise Snapshot Debugger؛ وضع النواة (TTD وضع مستخدم فقط3) |
1. الخلاصة أوّلاً
- يحفظ التفريغ «الحالة»؛ ويحفظ TTD «المسار». تنصّ الوثائق الرسميّة على أنّ التفريغات تميل إلى تفويت الحالة ومسار التنفيذ اللذين أدّيا إلى الفشل.1 إذا لم تخبرك صورة لحظة الانهيار بالسبب، فما تحتاجه تالياً ليس مزيداً من الصور بل تسجيلاً.
- التسجيل ثقيل. أثناء التسجيل تعمل العمليّة المستهدَفة أبطأ من 5 إلى 20 ضعفاً أو أسوأ، وينمو ملف التتبّع بـ 5 إلى 50 ميغابايت في الثانية بينما العمليّة نشطة، بلا سقف.24 ليست أداة تعلّقها بلا شرط على تطبيق طويل التشغيل.
- للتطبيقات طويلة التشغيل صمّم «ماذا تسجّل». يغطي الفصل 5 نقاط الدخول الأربع التي يوفّرها TTD.exe:
-ring/-maxFile(أبقِ آخر N ميغابايت فقط)، و-module(سجّل فقط أثناء تنفيذ وحدتك)، و-recordmode Manual(دع التطبيق يحدّد فاصل التسجيل)، و-monitor(سجّل كلّ إطلاق).2 - تدور إعادة التشغيل حول ثلاثة أمور: المواضع (
!tt)، والأحداث (dx @$curprocess.TTD.Events)، والاستعلامات (TTD.Calls/TTD.Memory). اجمعba(نقطة توقّف عند الوصول) معg-(تنفيذ عكسي)، فيجيب المصحّح «من كتب هذه القيمة أخيراً؟» مباشرة.5 - يحتوي التتبّع محتويات الذاكرة. يمكن أن يشمل معلومات شخصيّة وسرّيّة مثل مسارات الملفّات وبيانات السجلّ ومحتويات الذاكرة والملفّات.1 صمّم المشاركة والتخزين كما لملفّ سرّي.
- اعرف ما لا يستطيعه TTD قبل أن تبدأ. لا يسجّل وضع النواة، ولا يحقن نفسه في العمليّات المحميّة (PPL)، ولا يفصل نفسه بعد الرفق، ولا يمكن تعديل الذاكرة أثناء إعادة التشغيل.32
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 32، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. حيث لا يكفي التفريغ
تفريغ الانهيار نسخة من الذاكرة والسجلّات في لحظة انهيار العمليّة (أو إيقافها). كما وُصف في مقال تحليل SOS، يخبرك !clrstack أين انهار و!dumpheap -stat ما يستهلك الكومة. ما لا يستطيع إخبارك به هو ما حدث في الطريق إلى تلك الحالة.
flowchart TB
accTitle: ما يلتقطه التفريغ وما يلتقطه TTD
accDescr: يلتقط تفريغ الانهيار حالة لحظة الانهيار فقط، لا المسار الذي أدّى إليها. يحتفظ TTD بتنفيذ التعليمات كاملاً من بدء التسجيل إلى نهايته، لذا يُحفَظ المسار مع الحالة
dump["تفريغ الانهيار: الحالة في لحظة الانهيار"] --> q1["لا يُظهر لماذا صارت القيمة ما هي"]
ttd["تتبّع TTD: تنفيذ التعليمات في الفاصل المسجَّل"] --> q2["يمكن العودة إلى الموضع الذي كُتبت فيه القيمة"]
q1 -.-> gap["هذه الفجوة هي ما يطيل تحقيقات التشغيل الطويل"]
الشكل 1: التفريغ حالة؛ TTD هو المسار. في أخطاء التشغيل الطويل ما تريده عادة هو الأخير.
تبدو الحالات النموذجيّة كهذه.
- حقل واحد في بنية على الكومة يحمل قيمة مستحيلة. يُظهر التفريغ القيمة الفاسدة، لا من كتبها ولا متى.
- موضع الاستثناء معروف، لكن ليس لماذا كان الوسيط الممرَّر إليه غير صالح. بالمشي أبعد في المستدعين، لم تعد الدالّة التي أنتجت القيمة في الطريق على المكدّس.
- تنمو المقابض أو الذاكرة على مدى شهر. تفريغ واحد لا يقول سوى «إنّها تنمو»؛ مسار الاستدعاء الذي أنماها ليس هناك.
flowchart TB
accTitle: ما يفوته التفريغ في حالات التشغيل الطويل النموذجيّة
accDescr: تُلتقَط القيمة الفاسدة لا من كتبها، ويُلتقَط موضع الاستثناء لا الدالّة التي أنتجت الوسيط إذ لم تعد على المكدّس، ويُلتقَط نمو المورد لا المسار الذي أنماه؛ تشترك الأنواع الثلاثة في هذا
c1["حقل فاسد"] --> m1["لا سجلّ لمن كتبه أو متى"]
c2["استثناء على وسيط غير صالح"] --> m2["الدالّة التي أنتجت القيمة لم تعد على المكدّس"]
c3["مورد ينمو على مدى شهر"] --> m3["لا سجلّ لمسار الاستدعاء الذي أنماه"]
m1 --> same["نقطة مشتركة: الحالة موجودة، لكن لا مسار"]
m2 --> same
m3 --> same
الشكل 2: الحالات الثلاث كلّها لديها «حالة بلا مسار». مزيد من الصور لا يملأ المسار.
تقارن وثائق Microsoft نقاط القوّة والضعف لطرق التحقيق كالتالي.1
| الطريقة | نقاط القوّة | نقاط الضعف |
|---|---|---|
| التصحيح الحي | تفاعلي، يُظهر تدفّق التنفيذ، ويتيح تغيير الحالة | يوقف عمل المستخدم. يتطلّب جهداً لإعادة الإنتاج مراراً. غالباً غير قابل للاستخدام في الإنتاج. يصعب الرجوع من نقطة الفشل إلى السبب |
| التفريغات | لا تغييرات شيفرة مسبقاً. تدخّل منخفض، يمكن جمعها عند محفّز. عبء شبه صفري عند عدم الاستخدام | حتى مع لقطات متتالية، رؤية «الزمن المنقضي» خشنة |
| القياس عن بعد والسجلّات | خفيفة. مربوطة بسيناريوهات الأعمال | لا سجلّات على مسارات شيفرة غير متوقَّعة. عمق بيانات غير كافٍ، ومضمَّنة ثابتة في الشيفرة |
| TTD | قوي على الأخطاء المعقّدة. لا تغييرات شيفرة مسبقاً. يمكن إعادة التشغيل دون اتّصال أيّ عدد من المرّات ويسجّل كلّ شيء | عبء كبير أثناء التسجيل. قد يجمع بيانات أكثر ممّا يلزم. تكبر الملفّات |
هناك خاصّيّة مهمّة أخرى يشير إليها الدليل الرسمي لـ TTD. عندما يتوقّف المصحّح عند نقطة الفشل، غالباً ما تكون تلك النقطة داخل شيفرة معالجة أخطاء عدّة خطوات بعد السبب الحقيقي.5 يُؤخَذ التفريغ دائماً في هذا الموضع «بعد عدّة خطوات». مع TTD يمكنك الرجوع من هناك تعليمة تعليمة.
flowchart TB
accTitle: الفجوة بين نقطة الفشل والسبب الحقيقي
accDescr: نقطة الفشل التي يُؤخَذ عندها التفريغ غالباً داخل معالجة أخطاء عدّة خطوات بعد السبب الحقيقي، ومع TTD يمكنك الإرجاع من تلك النقطة تعليمة تعليمة إلى السبب
cause["السبب الحقيقي (التعليمات التي أفسدت القيمة)"] --> steps["عدّة خطوات إلى الأمام"]
steps --> fail["نقطة الفشل (استثناء، معالجة أخطاء)"]
fail -->|"تفريغ"| photo["مجمَّد هنا"]
fail -->|"TTD"| back["ارجع بـ p- / t- / g-"]
back --> cause
الشكل 3: التفريغ مجمَّد عند نقطة الفشل. يستطيع TTD المشي من نقطة الفشل إلى السبب.
3. كيف يعمل TTD وما تكلفته
3.1 ما الذي يُسجَّل
يحقن TTD محرّك تسجيل في العمليّة المستهدَفة ويسجّل التعليمات المنفَّذة، تعليمة تعليمة. بعبارة الوثائق الرسميّة، «يشفّر تتبّعاً كاملاً على مستوى التعليمات بمتوسّط أقلّ من بايت واحد لكلّ تعليمة»؛ عمليّاً يقع بين بت واحد وبايت واحد لكلّ تعليمة. البرامج التي تنفّذ أنواعاً قليلة من الدوال وتعالج بيانات قليلة تنتج تتبّعات أصغر؛ والعكس ينتج أكبر.24
ينتج التسجيل ملفّين.1
| الملفّ | الدور | الحجم التقريبي |
|---|---|---|
.run |
التتبّع نفسه. يخزّن تنفيذ التعليمات أثناء التسجيل | ينمو بـ 5 إلى 50 ميغابايت في الثانية أثناء النشاط. لا ينمو أثناء الخمول4 |
.idx |
الفهرس. بيانات مساعدة تتيح لـ WinDbg إعادة التشغيل والاستعلام عن الذاكرة بكفاءة. يُنشأ عند توقّف التسجيل، ويُولَّد أيضاً تلقائيّاً عندما يفتح WinDbg ملف .run |
1 إلى 2 ضعف حجم التتبّع4 |
flowchart TB
accTitle: التدفّق من تسجيل TTD إلى إعادة التشغيل
accDescr: يُحقَن محرّك تسجيل في العمليّة المستهدَفة ويُسجَّل تنفيذ التعليمات في ملف .run؛ عندما يفتح WinDbg ملف .run ينشئ فهرس .idx ويعيد التشغيل أماماً وخلفاً باستخدام المواضع والأحداث والاستعلامات
proc["العمليّة المستهدَفة"] --> inj["محرّك التسجيل محقون (TTDRecordCPU)"]
inj --> run[".run (سجلّ تنفيذ التعليمات)"]
run --> open["افتح في WinDbg"]
open --> idx["ولِّد .idx (فهرس)"]
idx --> play["أعد التشغيل أماماً وخلفاً بالمواضع والأحداث والاستعلامات"]
الشكل 4: التسجيل يتمّ بـ TTD.exe أو WinDbg؛ والقراءة بـ WinDbg. يكفي مشاركة ملف .run وحده.
يُعبَّر عن الزمن داخل ملف .run كـ«موضع». يأخذ شكل رقمين ست عشريّين مفصولين بنقطتين، مثل 12:0 أو 1A0:12F؛ النصف الأوّل رقم التسلسل (يقابل حدث تسلسل) والنصف الثاني العدد التقريبي للتعليمات منذ ذلك الحدث.6 يعني FFFFFFFFFFFFFFFE:0 نهاية التتبّع.7 تحتلّ المواضع مركز الفصل 6.
flowchart TB
accTitle: كيف تُعبَّر المواضع في تتبّع
accDescr: الموضع رقم تسلسل ست عشري وعدد خطوات مفصولان بنقطتين؛ البداية قرب 0، والنهاية تُعبَّر كـ FFFFFFFFFFFFFFFE:0، ويمكنك أيضاً الانتقال إلى موضع تقريبي بالنسبة المئويّة
pos["الموضع xx:yy (ست عشري)"] --> seq["xx: رقم التسلسل"]
pos --> step["yy: التعليمات منذ ذلك الحدث"]
seq --> tail["النهاية هي FFFFFFFFFFFFFFFE:0"]
step --> pct["تعمل أيضاً نسب مئويّة مثل !tt 50"]
الشكل 5: الموضع هو «رقم الحدث:عدد التعليمات». ليس زمن ساعة حائط، لكن يمكن تحويله إلى زمن ساعة حائط كما في الفصل 6.
3.2 التكلفة
تصف الوثائق الرسميّة نفسها TTD بأنّه «تقنيّة تدخّليّة».2 إليك التكلفة بالأرقام.
| البند | المحتوى |
|---|---|
| السرعة | تعمل العمليّة المستهدَفة أبطأ من 5 إلى 20 ضعفاً أو أسوأ أثناء التسجيل (حسب التطبيق وخيارات التسجيل). قد لا يُلاحَظ في واجهة المستخدم، لكنّه محسوس في عمليّات ثقيلة مثل مربع حوار فتح ملفّ23 |
| نمو الملفّ | 5 إلى 50 ميغابايت في الثانية أثناء النشاط. دقائق قليلة من التسجيل يمكن أن تبلغ عدّة غيغابايت. لا سقف مضبوط4 |
| نفاد القرص | إذا امتلأ القرص أثناء التسجيل، يكتب TTD الصفحة الأخيرة ثمّ ينتظر فعليّاً حتى يستطيع الكتابة مجدّداً. يواصل WinDbg إظهار مربع حوار التسجيل ولا يصدر خطأ ولا تحذيراً. النتيجة تتبّع ناقص4 |
| الذاكرة | يضيف التسجيل عبء وحدات CPU افتراضيّة إلى ذاكرة العمليّة المستهدَفة (افتراضيّاً 55 على x64/ARM64، و32 على x86). قلّله بـ -numVCpu فقط عندما تنقص الذاكرة2 |
| لا يمكن الفصل | ما إن يُرفَق، لا يستطيع TTD فصل نفسه. عندما تنهي التسجيل أغلق التطبيق أو أنهِ العمليّة. إذا كانت العمليّة أساسيّة للنظام يجب إعادة إقلاع نظام التشغيل2 |
flowchart TB
accTitle: تكلفة التسجيل وأثرها على التطبيقات طويلة التشغيل
accDescr: التباطؤ أثناء التسجيل، ونمو الملفّ، والانتظار الصامت عند نفاد القرص، والعجز عن الفصل بعد الرفق هي التكاليف الأربع التي تجعل تعليق TTD بلا شرط على تطبيق طويل التشغيل مستحيلاً
cost["تسجيل TTD"] --> slow["أبطأ من 5 إلى 20 ضعفاً"]
cost --> grow["ينمو بـ 5 إلى 50 ميغابايت في الثانية، بلا سقف"]
cost --> disk["ينتظر بصمت عندما ينفد القرص"]
cost --> stuck["لا يمكن الفصل بعد الرفق"]
slow --> no["التسجيل المتواصل بلا شرط غير قابل للتطبيق"]
grow --> no
disk --> no
stuck --> no
الشكل 6: أيّ واحدة من التكاليف الأربع تستبعد التسجيل المتواصل. لذلك يلزم «تصميم التسجيل» في الفصل 5.
3.3 ما لا يستطيعه
- وضع المستخدم فقط. يمكنه تسجيل تنفيذ وضع المستخدم لعمليّة فقط؛ الشيفرة التي تعمل في وضع النواة، مثل برامج التشغيل، لا يمكن تصحيحها.3
- العمليّات المحميّة. لا يستطيع TTD حقن نفسه في عمليّات Windows المحميّة مثل Protected Process Light (PPL).3
- إعادة التشغيل للقراءة فقط. يمكنك العودة في الزمن، لكن لا يمكنك تغيير التاريخ. أوامر قراءة الذاكرة تعمل؛ أوامر تعديلها لا تعمل.3
- عدم التوافق مع برامج مكافحة الفيروسات ومراقبة الذاكرة. لأنّ TTD يعلّق نفسه في العمليّة، يتعارض مع برمجيّات تتتبّع استدعاءات ذاكرة النظام أو تظلّلها. إذا أنتج التسجيل خطأ يشبه نقص الأذونات، عطِّل تلك البرمجيّات مؤقّتاً لعزل المشكلة. إطار Electron تعارض معروف أيضاً؛ حتى إذا نجح تسجيل قد تدخل العمليّة المستهدَفة في طريق مسدود أو تنهار.3
- لا يمكن إطلاق تطبيقات UWP وتسجيلها (الرفق بتطبيق UWP قيد التشغيل ممكن). «العمليّات غير المعتادة» التي تعمل في جلسة أخرى أو سياق أمان مختلف غير مدعومة حاليّاً أيضاً.8
flowchart TB
accTitle: ما لا يستطيع TTD تسجيله أو فعله
accDescr: لا يمكن تسجيل شيفرة وضع النواة والعمليّات المحميّة وتسجيل إطلاق تطبيقات UWP والعمليّات في جلسة أو سياق أمان آخر؛ ولا يمكن تعديل الذاكرة أثناء إعادة التشغيل؛ ويمكن أن تتعارض برامج مكافحة الفيروسات وElectron
no["قيود TTD"] --> g1["لا يمكن تسجيله"]
no --> g2["قيود وتعارضات"]
g1 --> k["شيفرة وضع النواة (برامج التشغيل وغيرها)"]
k --> ppl["عمليّات محميّة (PPL)"]
ppl --> uwp["تسجيل إطلاق UWP (الرفق ممكن)"]
uwp --> sess["جلسات وسياقات أمان أخرى"]
g2 --> ro["إعادة التشغيل للقراءة فقط"]
ro --> av["قد تتعارض مع مكافحة الفيروسات وElectron"]
الشكل 7: القيود نوعان: «لا يمكن التسجيل» و«تعارضات». الأخير يجب عزله لكلّ بيئة.
البند الأخير يُعثر من يريد تسجيل خدمة. تصف وثائق TTD.exe -attach بأنّه موجَّه لـ«التحقيق في الخدمات والتطبيقات طويلة التشغيل» و-monitor بأنّه يسجّل «في كلّ مرّة يبدأ فيها برنامج أو خدمة»،2 وهذا لا يتّسق ببساطة مع عبارة صفحة استكشاف الأخطاء. عمليّاً النهج الآمن التحقّق لكلّ بيئة بهذا الترتيب: أكّد أنّ ping.exe أو cmd.exe يمكن تسجيلهما في التكوين نفسه للإنتاج، ثمّ جرّب العمليّة المستهدَفة.8
4. التسجيل — واجهة WinDbg وTTD.exe
لنقطتي دخول للتسجيل: واجهة WinDbg، أو TTD.exe سطر الأوامر.
4.1 التسجيل من واجهة WinDbg
شغّل WinDbg كمسؤول (الارتفاع إلزامي لـ TTD1)، اختر File > Start debugging > Launch executable (advanced)، حدّد التنفيذي، وعلِّم Record with Time Travel Debugging. اختيار Configure and Record يتيح تعيين موضع ملف التتبّع وRecord subset of execution (احصر الوحدات المسجَّلة بقائمة مفصولة بفواصل مثل notepad.exe,kernelbase.dll). لعمليّة قيد التشغيل أصلاً اختر File > Start debugging > Attach to process وعلِّم كذلك Record Process with Time Travel Debugging.9
أثناء التسجيل يظهر مربع حوار صغير بزرّي «Stop and Debug» و«Cancel». عندما يخرج التطبيق (أو ينهار) يُغلَق التتبّع ويفتحه WinDbg تلقائيّاً ويبني الفهرس.5
flowchart TB
accTitle: تدفّق التسجيل من واجهة WinDbg
accDescr: في WinDbg المشغَّل كمسؤول اختر Launch executable (advanced) أو Attach to process، علِّم Record with Time Travel Debugging، اضبط موضع الحفظ ومرشّح الوحدة في Configure and Record، مرّ بمربع حوار التسجيل، وعندما يخرج التطبيق يُغلَق التتبّع ويُفهرَس تلقائيّاً
adm["ابدأ WinDbg كمسؤول"] --> pick["Launch executable (advanced) / Attach to process"]
pick --> chk["علِّم Record with Time Travel Debugging"]
chk --> cfg["Configure and Record: موضع الحفظ، مرشّح الوحدة"]
cfg --> rec["مربع حوار التسجيل (Stop and Debug)"]
rec --> fin["يخرج التطبيق، يُغلَق التتبّع ويُفهرَس تلقائيّاً"]
الشكل 8: التسجيل من الواجهة خمس خطوات. تخطَّ «ابدأ كمسؤول» فتتوقّف عند أوّل مربع حوار.
4.2 التسجيل بـ TTD.exe
عندما تحتاج إلى التسجيل على حاسوب لا يمكن تثبيت WinDbg عليه، أو لأتمتة التسجيل، استخدم TTD.exe وحده. يُثبَّت عبر App Installer من https://aka.ms/ttd/download، وبعد التثبيت يمكنك التحقّق منه بـ ttd.exe -help. للبيئات دون اتّصال توفّر Microsoft أيضاً إجراءً رسميّاً (مع سكربت PowerShell) لفكّ حزمة MSIX يدوياً واستخراج الثنائيّات فقط.2 يلزم التسجيل امتياز المسؤول ويُشغَّل عادة من موجه أوامر للمسؤول.2
هناك ثلاثة أوضاع تسجيل.2
flowchart TB
accTitle: أوضاع التسجيل الثلاثة لـ TTD.exe
accDescr: يطلق launch عمليّة جديدة بوسائط ويسجّلها، لكنّها تعمل بامتيازات مرتفعة. يرفق attach بعمليّة قيد التشغيل حسب PID. يسجّل monitor في كلّ مرّة يبدأ فيها البرنامج المحدَّد، ويحدث الإطلاق بامتيازات عاديّة
m["أوضاع تسجيل TTD.exe"] --> l["-launch: أطلق وسجّل"]
m --> a["-attach: ارفق بـ PID قيد التشغيل"]
m --> mo["-monitor: سجّل كلّ إطلاق"]
l -.-> lp["يُطلَق بامتيازات المسؤول"]
a -.-> ap["يحفظ الامتيازات العاديّة"]
mo -.-> mp["مسار إطلاق عادي، يناسب الأتمتة"]
الشكل 9: -launch وحده يستطيع تمرير وسائط، لكنّه يعمل بامتيازات مرتفعة. لتسجيل سلوك قريب من الإنتاج استخدم -attach أو -monitor.
:: أطلق وسجّل (الوضع الافتراضي. يمكن إغفال -launch)
TTD.exe -out C:\traces MyApp.exe --config prod.json
:: ارفق بعمليّة قيد التشغيل (أنشئ مجلّد الإخراج أولاً)
TTD.exe -attach 21440 -out C:\traces\MyApp.run
:: سجّل كلّ إطلاق (Ctrl+C يُنهي المراقبة. -out يتطلّب مساراً كاملاً)
TTD.exe -out C:\traces\ -monitor MyApp.exe
-launchهو الوضع الوحيد الذي يستطيع تمرير وسائط، لكن البرنامج يُطلَق بالامتيازات نفسها (المسؤول) لـ TTD.exe. للتطبيقات التي يتغيّر سلوكها مع الامتيازات سجّل بـ-attachأو-monitorلإبقاء الامتيازات العاديّة.2-attachيتطلّب وجود مجلّد الإخراج أصلاً. إذا حدّدت اسم ملفّ فلا يجوز أن يوجد ملفّ بذلك الاسم.2-monitorيثبّت برنامج تشغيل مراقبة إطلاق العمليّات ويسجّل في كلّ مرّة يبدأ فيها البرنامج المحدَّد (يمكن إعطاء أكثر من واحد). يبقى سارياً حتى إعادة الإقلاع ويُوقَف بـ Ctrl+C. مزاياه أنّك لا تحتاج إلى تجميع الإطلاق بنفسك، ويعمل الهدف بامتيازات عاديّة، ويناسب الأتمتة بالسكربت. إضافة-cmdLineFilter "string"تسجّل فقط الإطلاقات التي يحتوي سطر أوامرها تلك السلسلة.2-childrenيسجّل العمليّات الابن أيضاً، لكن لكلّ عمليّة ملف.runخاص، ولا يستطيع WinDbg فتح سوى واحد في كلّ مرّة.2
أثناء التسجيل تظهر واجهة صغيرة بزرّين: «Tracing Off» (أوقف التسجيل ودع التطبيق يتابع) و«Exit App» (أغلق التطبيق وأنهِ التسجيل). للأتمتة أخفِها بـ -noUI واقبل اتفاقية الترخيص بـ -accepteula.2 يُحفَظ سجلّ التسجيل في ملف .out في الموضع نفسه لملف .run، حيث يمكنك قراءة أزمنة ساعة الحائط لبدء التسجيل ونهايته، وطول جلسة التسجيل (زمن المحاكاة)، وما إذا كان إطلاقاً أو رفقاً، وإصدار نظام التشغيل. عندما يفشل تسجيل تظهر بعض رسائل الخطأ في ملف .out فقط.2
5. تصميم التسجيل للتطبيقات طويلة التشغيل
هذا قلب المقال. بالنظر إلى تكاليف الفصل 3، التقاط خطأ يحدث في وقت مجهول في عمليّة تعمل أيّاماً يتطلّب تصميم تسجيل. يوفّر TTD.exe أربع وسائل، وتختار حسب طبيعة العَرَض.
flowchart TB
accTitle: اختيار نطاق التسجيل حسب عَرَض تطبيق طويل التشغيل
accDescr: إذا لم تعرف متى سيحدث أبقِ الجزء الأخير فقط بمخزن حلقي؛ إذا عرفت أيّ وحدة مشتبَه بها سجّل تلك الوحدة فقط؛ إذا أمكن تعديل التطبيق حدّد الفاصل بواجهة التسجيل اليدوي؛ إذا ظهر عند الإقلاع أو إطلاقات معيّنة فقط سجّل كلّ إطلاق بوضع المراقبة
q{"طبيعة العَرَض؟"} -->|"مجهول متى يحدث"| ring["-ring / -maxFile: أبقِ النهاية فقط"]
q -->|"عند الإقلاع أو إطلاقات معيّنة فقط"| mon["-monitor: سجّل كلّ إطلاق"]
ring -->|"الوحدة المشتبَه بها واضحة"| mod["أضف -module"]
ring -->|"يمكن تعديل التطبيق"| man["حدّد الفاصل بـ -recordmode Manual"]
الشكل 10: الأربع ليست متنافية. الجمع بين -ring و-module هو التركيب الأكثر عمليّة للتطبيقات طويلة التشغيل.
5.1 المخزن الحلقي — أبقِ آخر N ميغابايت فقط
مع -ring يُكتَب التتبّع إلى مخزن حلقي بالحجم الذي يعطيه -maxFile، ولا ينمو الملفّ أبداً أبعد من ذلك الحدّ. ما يبقى هو الجزء الأخير فقط من التسجيل الذي يسع ذلك الحجم.2 وحدة -maxFile هي ميغابايت؛ في وضع المخزن الحلقي الافتراضي 2,048 ميغابايت، والحدّ الأدنى 1 ميغابايت، والأقصى 32,768 ميغابايت (افتراضي المخزن الحلقي في الذاكرة لعمليّة 32 بت هو 256 ميغابايت).2
:: ارفق بتطبيق مراقبة قيد التشغيل، وأبقِ آخر 4 غيغابايت فقط من التنفيذ
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run
:: عند اكتشاف العَرَض، أوقف التسجيل (التطبيق يتابع العمل)
TTD.exe -stop 21440
يقبل -stop اسم عمليّة أو PID أو all، ويوقف ذلك التسجيل. ينتظر -wait <seconds> حتى تنتهي كلّ جلسة تسجيل على النظام (-1 إلى ما لا نهاية)، ويُستخدم في سكربتات الأتمتة لفرض ترتيب «أوقف، ثمّ اجمع الملفّ».2
flowchart TB
accTitle: الخطّ الزمني لتسجيل مخزن حلقي
accDescr: يبدأ التسجيل عند الرفق، تُدفَع الأجزاء الأقدم خارج المخزن الحلقي، وعندما يظهر العَرَض ويُصدَر stop يبقى فقط أحدث maxFile كالتتبّع
s["ابدأ التسجيل بـ -attach -ring"] --> old["الفواصل الأقدم تُدفَع للخارج"]
old --> sym["يظهر العَرَض"]
sym --> stop["أوقف التسجيل بـ -stop"]
stop --> keep["يبقى أحدث maxFile فقط"]
keep -.-> note["حدّد الحجم بحيث تسع اللحظات قبل العَرَض في المخزن"]
الشكل 11: المخزن الحلقي جهاز لإبقاء «اللحظات قبل العَرَض». اعمل -maxFile إلى الخلف من «الزمن من ملاحظة العَرَض إلى الإيقاف، مضروباً في النمو لكلّ ثانية».
هناك نقطتا تصميم.
- اجعل المخزن كبيراً بما يكفي لامتصاص الزمن من الملاحظة إلى الإيقاف. في عمليّة نشطة ينمو التتبّع بـ 5 إلى 50 ميغابايت في الثانية،4 لذا يقابل مخزن 4 غيغابايت دقيقة إلى دقيقتين تحت حمل ثقيل وأزيد قليلاً من عشر دقائق تحت حمل خفيف. تحتاج آلية تُبقي التأخير بين اكتشاف العَرَض (سطر سجلّ معيّن، عتبة عدّاد، تنبيه من المراقبة) و
-stopداخل تلك النافذة. - الإيقاف لا يفصل. يوقف
-stopالتسجيل، لكن TTD لا يفصل نفسه عن العمليّة المستهدَفة.2 إنهاء التسجيل تماماً يتطلّب إنهاء العمليّة، لذا أدخل «أعد التشغيل في نافذة الصيانة التالية بعد جمع التتبّع» في إجراء التشغيل.
sequenceDiagram
accTitle: إيقاف تسجيل مخزن حلقي بالتنسيق مع المراقبة
accDescr: عندما يكتشف جانب المراقبة العَرَض عبر السجلّات أو العدّادات يستدعي TTD.exe stop، ويجمع ملف .run المكتمل، ويعيد تشغيل العمليّة في نافذة الصيانة التالية لإزالة TTD
participant W as المراقبة (السجلّات، العدّادات)
participant T as TTD.exe
participant P as العمليّة المستهدَفة
W->>W: اكتشف العَرَض
W->>T: -stop PID
T->>P: أوقف التسجيل (العمليّة تتابع)
T-->>W: يُكمَل .run
W->>W: اجمع .run، شفِّر، واحفظ
W->>P: أعد التشغيل في نافذة الصيانة التالية
الشكل 12: فقط عندما يسع التأخير بين الاكتشاف و-stop في المخزن تبقى اللحظات قبل العَرَض في التتبّع. إعادة التشغيل جزء من التشغيل.
5.2 مرشّح الوحدة — سجّل فقط أثناء تشغيل شيفرتك
يسجّل -module <module name> الوحدة المحدَّدة فقط (التنفيذي نفسه أو DLL محمَّلة؛ يمكن إعطاء أكثر من واحدة) والشيفرة التي تستدعيها تلك الوحدة. تعمل العمليّة المستهدَفة بالسرعة الكاملة حتى تنفَّذ شيفرة في الوحدة المحدَّدة؛ يبدأ التسجيل عندما يدخل التنفيذ الوحدة، ويتوقّف عندما يخرج، وتعود العمليّة إلى السرعة الكاملة. لأنّ تشغيل التسجيل وإيقافه مكلفان، يبقى التسجيل شغّالاً بينما تستدعي الوحدة المحدَّدة وحدات أخرى في العمليّة.2
:: سجّل فقط أثناء تشغيل DLL منطق القياس لدينا (مع المخزن الحلقي)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\
يتخطّى تتبّع صُنع بهذا الشكل ببساطة الفواصل التي كان التسجيل فيها متوقّفاً، فيعامل «التعليمات التالية» كأوّل تعليمة بعد استئناف التسجيل، وتصحّحه لا يختلف عن تتبّع العمليّة كلّها.2 في التطبيقات طويلة التشغيل يميل حلقة خمول واجهة المستخدم والمعالجة الداخليّة للإطار إلى حساب معظم التعليمات المنفَّذة، لذا فإنّ حصر التسجيل في وحدتك وحدها يقلّل العبء وحجم الملفّ كثيراً.
flowchart TB
accTitle: كيف يتصرّف التسجيل المرشَّح بالوحدة
accDescr: تعمل العمليّة المستهدَفة بالسرعة الكاملة خارج الوحدة المحدَّدة؛ يبدأ التسجيل عند دخول شيفرة الوحدة المحدَّدة، ويتابع بينما تستدعي تلك الوحدة وحدات أخرى، ويتوقّف عندما يخرج التنفيذ من الوحدة
out1["خارج الوحدة المحدَّدة: سرعة كاملة"] --> in1["ادخل الوحدة المحدَّدة: يبدأ التسجيل"]
in1 --> callee["وحدات أخرى تستدعيها: يتابع التسجيل"]
callee --> out2["اخرج من الوحدة المحدَّدة: يتوقّف التسجيل"]
out2 --> out1
الشكل 13: لأنّ واجهات Win32 ووقت التشغيل التي تستدعيها DLL تُسجَّل أيضاً، يكفي هذا لتتبّع «ما سلّمته شيفرتنا إلى نظام التشغيل».
5.3 التسجيل اليدوي — دع التطبيق يحدّد الفاصل
مع -recordmode Manual تواصل العمليّة العمل بالسرعة الكاملة حتى بعد حقن TTD، ويحدث التسجيل فقط عندما يستدعي البرنامج واجهة التسجيل داخل العمليّة لـ TTD (الافتراضي، Automatic، يسجّل من لحظة الحقن).2 وثائق الواجهة والعيّنات في مستودع WinDbg-Samples على GitHub.10
إذا أمكن تعديل التطبيق فهذا أقلّ نهج هدراً. يمكنك بناء منطق يبدأ التسجيل عندما يعلم التطبيق نفسه أنّ شذوذاً وشيك ويتوقّف عندما تعود الأمور إلى الطبيعي: «فشلت ثلاث إعادات محاولة اتّصال متتالية»، «تجاوز تراكم الطابور العتبة»، وما شابه. في تكوين عمليّة مشرفة كالذي غُطي في مقال Job Object يمكن للمشرف أن يقرّر الفاصل بدل ذلك.
5.4 وضع المراقبة — سجّل كلّ إطلاق
للأخطاء التي تظهر فقط فور الإقلاع أو عند إطلاقات معيّنة، يناسب -monitor. يصف جدول المقارنة الرسمي أيضاً وضع المراقبة بأنّه موجَّه لـ«التقاط المشاكل المتقطّعة ومشاكل الإقلاع».2 عند تسجيل البرنامج نفسه مرّات كثيرة تصبح أسماء الملفّات التسلسليّة الافتراضيّة (MyApp01.run وMyApp02.run وما شابه) غير فعّالة لأنّ الملفّات الموجودة يجب مسحها، لذا استخدم -timestampFilename لأسماء بطابع زمني. يمكن حصر عدد التسجيلات المتزامنة بـ -maxConcurrentRecordings.2
flowchart TB
accTitle: كيف يتصرّف وضع المراقبة
accDescr: يثبّت خيار المراقبة برنامج تشغيل مراقبة إطلاق العمليّات، ويضيّق الهدف بمرشّح سطر الأوامر في كلّ مرّة يبدأ فيها البرنامج المحدَّد، ويسجّله، وينشئ ملف تتبّع منفصلاً لكلّ إطلاق، ويتابع حتى Ctrl+C أو إعادة إقلاع
drv["ثبّت برنامج تشغيل مراقبة الإطلاق"] --> launch["اكتشف إطلاقاً للبرنامج المحدَّد"]
launch --> filt{"يطابق -cmdLineFilter؟"}
filt -->|"نعم"| rec["سجّل ذلك الإطلاق (ملف منفصل لكلّ إطلاق)"]
filt -->|"لا"| skip["لا تسجّل"]
rec --> next["انتظر الإطلاق التالي (حتى Ctrl+C أو إعادة الإقلاع)"]
skip --> next
الشكل 14: وضع المراقبة «يكمن للإطلاق». يناسب الأخطاء التي تظهر عند كلّ إطلاق والأخطاء التي يمكن تضييقها بشروط الإطلاق.
5.5 أين تضع القرص
للتسجيلات طويلة التشغيل ضع التتبّعات على مجلّد مخصّص وأدخل نمو ملف .run في مراقبتك. كما لوحظ في القسم 3.2، عندما يمتلئ القرص ينتظر التسجيل بصمت بلا خطأ. الحلّ الرسمي بدائي بالقدر نفسه: «راجع المساحة الحرّة في المستكشف» و«تحقّق من أنّ ملف .run ينمو بانتظام».4 بلا مراقبة المساحة الحرّة تنتهي بتتبّع ناقص لم تُكتَب فيه اللحظة التي أردتها أكثر.
flowchart TB
accTitle: كيف ينتج نفاد القرص تتبّعاً ناقصاً
accDescr: عندما ينفد القرص أثناء التسجيل يكتب TTD الصفحة الأخيرة وينتظر بصمت بلا خطأ ولا تحذير، لذا لا يُسجَّل العَرَض الذي يحدث بعد ذلك، فيبقى تتبّع ناقص يُفتَح لكن ينقصه الجزء الحاسم. امنعه بمجلّد مخصّص ومراقبة نمو .run
full["ينفد القرص"] --> wait["يكتب الصفحة الأخيرة وينتظر بصمت"]
wait --> none["لا خطأ، لا تحذير"]
none --> sym["يحدث العَرَض بعد ذلك"]
sym --> inc["تتبّع ناقص بلا العَرَض"]
guard["مجلّد مخصّص + مراقبة نمو .run"] -.->|"يمنع"| full
الشكل 15: تتبّع «يُفتَح لكن ينقصه الجزء الحاسم» يولد من فجوة في مراقبة القرص.
6. إعادة التشغيل — المواضع والأحداث والإرجاع
6.1 الفتح
عندما تفتح ملف .run في WinDbg، إن لم يوجد فهرس يعمل !index تلقائيّاً ويبني ملف .idx بينما يعدّ الإطارات المفتاحيّة (مواضع في التتبّع مولَّدة تلقائيّاً للفهرسة؛ التتبّعات الأكبر فيها أكثر منها). كلّما كبر التتبّع طال الوقت.5 يمكن فحص حالة الفهرس بـ !index -status؛ إذا بلّغ أيّ شيء غير «Index file loaded» فأعد بناءه بـ !index -force. إذا فشل ذلك أيضاً أغلق المصحّح واحذف ملف .idx وأعد فتح ملف .run. إعادة بناء الفهرس لا تعدّل ملف .run، لذا لا تُفقَد بيانات.8
تحذير واحد: عندما حُسِّنت فهرسة التتبّعات الكبيرة في TTD 1.11.611 تغيّر تنسيق الفهرس، وتحتاج التتبّعات الموجودة إلى إعادة فهرسة.11 إذا حملت ملفّات .idx قديمة فهنا تتعثّر.
flowchart TB
accTitle: فحص الفهرس وإعادة بنائه
accDescr: بعد فتح التتبّع راجع الحالة بـ !index -status؛ إن كانت أيّ شيء غير Index file loaded فأعد البناء بـ !index -force، وإذا فشل ذلك أيضاً أغلق المصحّح واحذف ملف .idx وأعد فتح .run. إعادة البناء لا تعدّل ملف .run
open["افتح التتبّع"] --> st["!index -status"]
st -->|"Index file loaded"| ok["انتقل إلى التحليل"]
st -->|"أيّ شيء آخر"| force["أعد البناء بـ !index -force"]
force -->|"يفشل"| del["أغلق، احذف .idx، أعد فتح .run"]
del --> ok
force -->|"ينجح"| ok
الشكل 16: إعادة بناء الفهرس لا تلمس ملف .run. عند الشك احذفه وأعد الفتح.
6.2 الانتقال حسب الموضع
تمرير موضع إلى !tt ينتقل إلى تلك النقطة في الزمن.6
!tt 0 ; بداية التتبّع
!tt 50 ; موضع 50% تقريباً
!tt 100 ; نهاية التتبّع
!tt 1A0:12F ; إلى الموضع 1A0:12F
الموضع زوج من أرقام ست عشريّة، رقم التسلسل:عدد الخطوات.6 يملك كائن Position الخصائص Percent (جزء التتبّع)، وSequence، وSteps، إضافة إلى SeekTo() الذي ينتقل إلى ذلك الموضع، وToSystemTime() الذي يعيد زمن ساعة الحائط التقريبي (UTC).7 في تحقيقات التشغيل الطويل يصنع ToSystemTime() الفرق، لأنّه يتيح مطابقة الطوابع الزمنيّة في سجلّ التطبيق مع مواضع في التتبّع. تحمل نتائج TTD.Calls أيضاً SystemTimeStart / SystemTimeEnd.12
flowchart TB
accTitle: مطابقة المواضع مع طوابع السجلّ الزمنيّة
accDescr: بدءاً من طابع زمني في سجلّ التطبيق اتبع زمن ساعة الحائط التقريبي لكائن Position لتحديد الموضع في التتبّع، وانتقل إليه بـ SeekTo، واقرأ التنفيذ حوله
log["سجلّ التطبيق: وقت الشذوذ"] --> match["جد موضعاً قريب ToSystemTime"]
match --> seek["انتقل إليه بـ SeekTo"]
seek --> read["اقرأ الاستدعاءات والقيم المحيطة"]
الشكل 17: «ماذا كان يفعل قبيل سطر السجلّ هذا؟» يمكن البحث عنه عبر التقابل بين المواضع وزمن ساعة الحائط.
المواضع مفيدة للمشاركة أيضاً. عندما تسلّم تتبّعاً لزمیل ألحق موضع !tt x:y فيبدأ النظر من النقطة الزمنيّة نفسها. كتابة نطاقات المواضع في تقارير الأخطاء ممارسة موصى بها رسميّاً أيضاً.2
6.3 الإرجاع
إلحاق - بأوامر الخطوة المعتادة يتحرّك إلى الخلف في الزمن.13
| الأمر | المعنى | زرّ الشريط |
|---|---|---|
p- |
ارجع تعليمة واحدة (أو سطر مصدر واحد). يُعدّ استدعاء دالّة خطوة واحدة | Step Over Back |
t- |
ارجع تعليمة واحدة (أو سطر مصدر واحد). يدخل استدعاءات الدوال | Step Into Back |
g- |
نفّذ عكسياً. يتوقّف عند إصابة نقطة توقّف، أو عند حدث، أو عند بداية التتبّع | Go Back |
الأحداث التي توقف g- هي نفسها التي توقف g الأمامي.13 بعبارة أخرى، اضبط ba (نقطة توقّف عند الوصول) أو bp وشغّل g- فتعود مباشرة إلى «آخر موضع صمد فيه ذلك الشرط». هذه الحركة الأساسيّة للفصل 7.
flowchart TB
accTitle: الاختيار بين أوامر العكس
accDescr: يرجع p- خطوة واحدة عبر استدعاءات الدوال، ويرجع t- تعليمة واحدة داخل الدوال، ويرجع g- مباشرة إلى نقطة توقّف أو حدث أو بداية التتبّع. ما يوقف g الأمامي يوقف g- أيضاً
cur["الموضع الحالي"] -->|"p-"| over["ارجع خطوة واحدة عبر الاستدعاءات"]
cur -->|"t-"| into["ارجع تعليمة واحدة داخل الدوال"]
cur -->|"g-"| run["ارجع مباشرة إلى شرط التوقّف التالي"]
run -.-> stop["ba / bp / حدث / بداية التتبّع"]
الشكل 18: للنظر بعناية في الجوار استخدم t-؛ للقفز إلى سبب بعيد استخدم ba + g-.
6.4 الدخول عبر الأحداث
إذا لم تكن متأكّداً من أين تبدأ القراءة فابدأ بقائمة الأحداث. يسرد @$curprocess.TTD.Events إنشاء الخيوط وإنهاءها، وتحميل الوحدات وتفريغها، والاستثناءات كأحداث.14
dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)
يحتوي حدث استثناء الموضع والنوع (Software / Hardware) ورمز الاستثناء وعدّاد البرنامج في ذلك الوقت، والنقر على رابط [Time Travel] في المخرجات ينتقل إلى ذلك الموضع.15 يتبع الدليل الرسمي هذا التدفّق: يقفز إلى موضع انتهاك وصول (0xc0000005)، ويشتبه في فساد المكدّس لأنّ مؤشّر المكدّس ومؤشّر القاعدة يختلفان، ويرجع ثلاث تعليمات بـ t- للتحقّق من القيم.5 تصوّر نافذة Timelines في WinDbg الاستثناءات ونقاط التوقّف ووصولات الذاكرة واستدعاءات الدوال كخطّ زمني، والنقر المزدوج على استثناء يصدر SeekTo() نفسه.16
6.5 الخيوط والمواضع
يُظهر !positions كلّ خيط نشط في الموضع الحالي مع موضع كلّ خيط في التتبّع.17 هناك مطبّ هنا. تبديل الخيوط بـ ~<number>s لا يحرّك الموضع في التتبّع. لا يتغيّر الموضع الذي يستخدمه المصحّح لقراءة الذاكرة، لذا للنظر إلى ذاكرة خيط آخر «في تلك النقطة الزمنيّة» انتقل هناك برابط الموضع في مخرجات !positions أو بـ !tt x:y.13
flowchart TB
accTitle: المسار الأساسي عبر إعادة تشغيل
accDescr: افتح التتبّع وابنِ الفهرس، وانتقل من قائمة الأحداث إلى موضع الاستثناء، وارجع خطوة إلى السبب، وإن لزم انتقل إلى موضع خيط آخر بـ positions
open["افتح التتبّع وفهرسه"] --> ev["جد الاستثناء في TTD.Events"]
ev --> seek["انتقل إلى الموضع بـ [Time Travel]"]
seek --> back["ارجع بـ t- / p- / g-"]
back --> th["راجع مواضع الخيوط الأخرى بـ !positions"]
th -.-> caution["~s لا يحرّك موضع التتبّع"]
الشكل 19: «ادخل عبر حدث، وامشِ إلى الخلف» هو المسار الأساسي عبر إعادة التشغيل. تبديل الخيوط ليس تحريك مواضع.
7. إيجاد «متى» بالاستعلامات — TTD.Calls وTTD.Memory
القوّة الحقيقيّة لإعادة التشغيل أنّ يمكنك الاستعلام عن التتبّع كلّه. تُعرَض كائنات TTD عبر نموذج بيانات المصحّح (أمر dx)، ويمكنك ترشيحها وفرزها وتجميعها بأسلوب LINQ.18
7.1 TTD.Calls — البحث عن استدعاءات الدوال
يجمع @$cursession.TTD.Calls("module!symbol") استدعاءات الدالّة المحدَّدة من التتبّع كلّه. البدل مسموح، ويحمل كلّ استدعاء مواضع البداية والنهاية (TimeStart / TimeEnd)، ومعرِّف الخيط (مع UniqueThreadId لا يُعاد استخدامه أبداً)، والوسائط (Parameters[])، والقيمة المرجَعة (ReturnValue)، وعنوان الرجوع (ReturnAddress).12
مثال الوثائق الرسميّة هو GetLastError. جمّع الاستدعاءات ذات القيمة المرجَعة غير الصفريّة حسب رمز الخطأ فتحصل على قائمة بأيّ أخطاء حدثت وكم مرّة أثناء التتبّع.18
dx -g @$cursession.TTD.Calls("kernelbase!GetLastError").Where(x => x.ReturnValue != 0).GroupBy(x => x.ReturnValue).Select(x => new { ErrorNumber = x.First().ReturnValue, ErrorCount = x.Count() }).OrderByDescending(p => p.ErrorCount),d
لـ«من أين استُدعي آخر MessageBox؟» خذ آخر استدعاء بـ OrderBy(c => c.TimeStart).Last() وانتقل هناك عبر رابط [Time Travel] لـ TimeStart.18
flowchart TB
accTitle: بناء استعلام TTD.Calls
accDescr: اجمع الاستدعاءات باسم الدالّة، رشّح بالقيمة المرجَعة أو الوسائط، جمّع حسب رمز الخطأ وما شابه، افرز حسب الزمن، وانتقل إلى موضع الاستدعاء الذي تريده عبر رابط Time Travel
calls["TTD.Calls (اسم الدالّة، البدل)"] --> where["Where: رشّح بالقيمة المرجَعة أو الوسائط"]
where --> group["GroupBy: جمّع حسب رمز الخطأ وغيرها"]
group --> order["OrderBy: افرز حسب الزمن"]
order --> jump["انتقل عبر [Time Travel] على TimeStart"]
الشكل 20: طريقة استخدام الاستعلامات أن تبدأ لا من «أين» بل من «متى، وكم مرّة، وبأيّ وسائط».
كلمة عن الرموز. يحدّد TTD عدد أنواع وسائط الدالّة وأنواعها ونوع إرجاعها واصطلاح الاستدعاء من معلومات رموز PDB. بالرموز الخاصّة تحصل على اسم الدالّة والوسائط الصحيحة. بالرموز العامّة فقط تحصل على اسم الدالّة ووسائط افتراضيّة (أربعة أعداد صحيحة 64 بت بلا إشارة). وحدة بلا رموز أصلاً تحصل على اسم الدالّة UnknownOrMissingSymbols.1218 لأنواع PDB وكيفيّة تخزينها انظر «ما هو PDB؟».
flowchart TB
accTitle: توفّر الرموز ونتائج TTD.Calls
accDescr: بالرموز الخاصّة تحصل على اسم الدالّة والوسائط الصحيحة؛ بالرموز العامّة فقط تحصل على اسم الدالّة والوسائط الافتراضيّة الأربع 64 بت؛ بلا رموز يصبح اسم الدالّة UnknownOrMissingSymbols
sym{"رموز الوحدة؟"} -->|"رموز خاصّة"| full["اسم الدالّة + وسائط وقيمة إرجاع صحيحة"]
sym -->|"رموز عامّة"| pub["اسم الدالّة + وسائط افتراضيّة (4 أعداد 64 بت)"]
sym -->|"لا شيء"| unk["UnknownOrMissingSymbols"]
الشكل 21: ما إذا احتفظت بملفّات PDB لوحداتك يحدّد مباشرة مدى فائدة الاستعلامات.
يشمل Calls حساباً، لذا كلّما كبر التتبّع طال الوقت وارتفع استخدام CPU. تُخزَّن النتائج مؤقّتاً في الذاكرة، لذا تكون الاستعلامات الثانية فما بعد للدالّة نفسها أسرع.12 عندما لا يعيد استعلام شيئاً هناك أربعة أسباب: كيفيّة كتابة الاستدعاء (راجع اسم الوحدة بأمر x؛ إذا عاد بأحرف كبيرة فاستخدم ذلك)، DLL المستهدَفة غير محمَّلة بعد في ذلك الموضع (انتقل إلى موضع بعد التحميل وشغّله مجدّداً)، الدالّة مضمَّنة (لا يمكن تتبّعها)، أو البدل واسع جدّاً (ضيّقه).18
7.2 TTD.Memory — البحث عن وصولات الذاكرة
يجمع @$cursession.TTD.Memory(start address, end address, "access type") الوصولات إلى نطاق الذاكرة المحدَّد من التتبّع كلّه. الأنواع r (قراءة)، وw (كتابة)، وrw، وe (تنفيذ)، وrwe، وec (تنفيذ/تغيير).19 يُجاب «من كتب هذا المتغيّر أخيراً؟» بالجمع بـ "w" وأخذ .Last() والانتقال إلى ذلك الموضع.5
dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()
إذا احتجت فقط إلى البحث أماماً أو خلفاً من الموضع الحالي فـ @$curprocess.TTD.PrevMemoryAccess("w", address, size) / NextMemoryAccess أخفّ وتقبل نطاقات متعدّدة في آن. يمكن إيجاد الموضع الذي تغيّر فيه سجلّ بـ @$curthread.TTD.PrevRegisterWrite("rcx").6
7.3 ba + g- — جعل المصحّح يجيب «من أفسده؟»
تكثيف الإجراء المعروض في الدليل الرسمي إلى شكل قابل للاستخدام لتحقيقات التشغيل الطويل يعطي التالي.5
- انتقل إلى موضع الاستثناء بـ
TTD.Events - ارجع بـ
t-وافترض أيّ متغيّر يحمل القيمة الفاسدة - احصل على عنوان ذلك المتغيّر بـ
dx &variable - اضبط نقطة توقّف كتابة بـ
ba w4 <address> - شغّل
g-للرجوع مباشرة إلى الموضع الذي كُتب فيه ذلك المتغيّر أخيراً - تحقّق ممّا إذا كانت تلك النقطة (أو بضع تعليمات أسبق) هي السبب. إذا جاءت القيمة المكتوبة من متغيّر آخر فاضبط
baعلى ذلك المتغيّر وشغّلg-مجدّداً - كرّر حتى تصل إلى التعليمات التي أفسدته
flowchart TB
accTitle: تتبّع أصل قيمة بـ ba وg-
accDescr: اضبط نقطة توقّف كتابة على عنوان القيمة الفاسدة ونفّذ عكسياً، توقّف عند التعليمات التي كتبته أخيراً، وإذا جاءت تلك القيمة من متغيّر آخر كرّر الخطوات نفسها حتى تصل إلى التعليمات التي أفسدته
bad["حدّد عنوان القيمة الفاسدة"] --> ba["اضبط نقطة توقّف كتابة بـ ba w"]
ba --> gb["نفّذ عكسياً بـ g-"]
gb --> writer["توقّف عند التعليمات التي كتبته أخيراً"]
writer --> q{"هل جاءت القيمة من متغيّر آخر؟"}
q -->|"نعم"| bad
q -->|"لا"| found["التعليمات المفسدة = السبب"]
الشكل 22: تحقيق ينتهي عند «إنّه فاسد» بتفريغ يتقدّم آليّاً إلى «من أفسده» بـ TTD.
تضيف TTD 1.11.553 فما بعد أيضاً @$curframe.TTD.VariableHistory() الذي يعيد تاريخ قيم المتغيّرات المحلّيّة لإطار. يُظهر جدولاً لأسماء المتغيّرات وأيّ قيم حملها كلّ متغيّر على أيّ نطاقات مواضع.11 في حالات مثل فساد المكدّس حيث تريد أن تعرف «منذ متى والقيمة خاطئة» يفيد لتضييق أين تضبط ba أوّلاً.
8. تطبيق هذا على أخطاء التشغيل الطويل
نطبّق الآن هذه الأدوات على الأنواع الثلاثة المذكورة في البداية.
flowchart TB
accTitle: أنواع أخطاء التشغيل الطويل ونقطة دخول TTD لكلّ منها
accDescr: للاستثناءات المتقطّعة وفساد البيانات ارجع من الحدث بـ ba وg-؛ لنمو الموارد طابق استدعاءات الحصول والإفلات بـ TTD.Calls؛ لعدم الاستجابة وسلاسل الانتظار اتبع المواضع وأزمنة ساعة الحائط لاستدعاءات واجهات الانتظار
t1["استثناءات متقطّعة وفساد بيانات"] --> a1["TTD.Events، ثمّ ba + g-"]
t2["نمو المقابض والذاكرة"] --> a2["طابق الحصول والإفلات بـ TTD.Calls"]
t3["عدم الاستجابة وسلاسل الانتظار"] --> a3["!positions وأزمنة ساعة الحائط لواجهات الانتظار"]
a2 -.-> pre["سجّل بـ -module محصوراً في DLL الخاصّة بك"]
الشكل 23: تختلف نقطة الدخول حسب النوع. ما يشتركن فيه تضييق نطاق التسجيل قبل أن تبدأ القراءة.
النوع 1: الاستثناءات المتقطّعة وفساد البيانات. أبقِ اللحظات قبل العَرَض بالمخزن الحلقي في القسم 5.1، واقفز إلى موضع الاستثناء من قائمة الأحداث في القسم 6.4، وتتبع أصل القيمة بـ ba + g- في القسم 7.3. الفرق عن تحقيق التفريغ أنّ التحقيق لا ينتهي عندما تجد «المتغيّر الفاسد»؛ من هناك تتقدّم آليّاً إلى الخلف.
النوع 2: نمو المقابض والذاكرة. هذا نوع الحالة المشرَّح في «تسرّب مقبض ينهار بعد شهر». لا يمكنك تسجيل شهر كامل، لذا احصر التسجيل في DLL الخاصّة بك بـ -module في القسم 5.2، واجمعه مع -ring، وأبقِ «الدقائق القليلة أثناء النمو». على التتبّع اجمع TTD.Calls("kernelbase!CreateFileW") وTTD.Calls("kernelbase!CloseHandle") وطابق القيمة المرجَعة لـ CreateFileW (قيمة المقبض) مع الوسيط الأوّل لـ CloseHandle (Parameters[0]). قيمة ReturnValue لـ CloseHandle علم نجاح منطقي، لذا لا تفيد للمطابقة. إذا تُرك مقبض دون إغلاق يعطيك ReturnAddress (المستدعي) لذلك الاستدعاء لـ CreateFileW المستدعي الذي يتسرّب.12 مع ذلك، رصد ما إذا وُجد تسرّب ومدى كبره أرخص بـ Application Verifier أو عدّادات المقابض؛ تقسيم العمل الصحيح إدخال TTD عند مرحلة تثبيت أيّ مسار يتسرّب. لاحظ أنّ التسجيل مع تفعيل Application Verifier يدهور أداء إعادة التشغيل بوضوح بسبب كيفيّة استخدام الذاكرة، لذا عطِّله أثناء التسجيل.8
النوع 3: «لا يستجيب» وسلاسل الانتظار. كما كُتب في «ما الذي يعنيه «لا يستجيب» حقّاً»، التجمّد سؤال عمّن ينتظر من. لا ينمو التتبّع أثناء الخمول،4 لذا لا يظهر زمن انتظار خيط ينتظر في النواة كتعليمات. ما يظهر هو الاستدعاء قبيل دخول الانتظار والتعليمات بعد رجوعه. انظر إلى موضع كلّ خيط بـ !positions،17 وصفّ «أيّ خيط بدأ انتظار ماذا، ومتى» من SystemTimeStart / SystemTimeEnd لـ TTD.Calls("kernelbase!WaitForSingleObject") وما شابه؛ مطابقاً لطوابع السجلّ الزمنيّة يتيح ذلك إعادة بناء سلسلة الانتظارات.12 «أخطاء تعتمد على الترتيب» مثل DllMain وقفل المحمِّل أو الإيقاظات الزائفة لمتغيّرات الشرط مجال يناسب فيه TTD، الذي يسجّل الترتيب نفسه.
9. TTD مع تطبيقات .NET
تنصّ وثائق TTD الرسميّة على أنّ الشيفرة المُدارة يمكن تصحيحها بـ TTD في WinDbg باستخدام امتداد SOS (sos.dll) العامل في وضع 64 بت.1 تحميله هو نفسه في الفصل 3 من مقال تحليل SOS (.loadby sos coreclr أو التحميل التلقائي)، و!clrstack و!pe يعملان عند كلّ موضع في التتبّع. المسار الأساسي الانتقال إلى موضع الاستثناء بـ TTD.Events ثمّ قراءة المكدّس المُدار بـ !clrstack.
flowchart TB
accTitle: إعداد قراءة تتبّع TTD لتطبيق .NET
accDescr: افتح تتبّع TTD في WinDbg، حمِّل امتداد SOS بـ 64 بت، انتقل إلى موضع حدث الاستثناء، واقرأ الحالة المُدارة بـ !clrstack و!pe. استخدم TTD.Calls للاستدعاءات عبر الحدود الأصليّة
run["تتبّع TTD (.run)"] --> wd["WinDbg"]
wd --> sos["امتداد SOS (64 بت)"]
sos --> ev["انتقل إلى موضع الاستثناء بـ TTD.Events"]
ev --> clr["اقرأ بـ !clrstack / !pe"]
wd -.-> calls["TTD.Calls للاستدعاءات عبر الحدود الأصليّة"]
الشكل 24: الحالة المُدارة عبر SOS، وبحث الاستدعاءات عند الحدود الأصليّة. افصل الأدوار فلا تتوه.
تحذيران. أوّلاً ما تضمنه Microsoft هو «SOS في وضع 64 بت». لا شيء مذكور عن تطبيقات .NET المبنيّة لـ x86، لذا شغّل هدف التحقيق كـ x64 إن استطعت. ثانياً يعتمد TTD.Calls على معلومات رموز PDB.12 لا تعوّل على البحث عن دوال مُدارة مترجمة بـ JIT بالاسم؛ الاستخدام الموثوق تتبّع الاستدعاءات عبر الحدود الأصليّة مثل أهداف P/Invoke في DLL أصليّة، وCOM، وواجهات Win32. مشاكل الكومة المُدارة مثل تلك في «تمييز تأخير GC عن تسرّب ذاكرة في .NET» تُتابَع أوّلاً بـ dotnet-counters / dotnet-gcdump / !gcroot على تفريغ، ويخرج TTD عند المرحلة التي تتورّط فيها حدود أصليّة.
10. الاختيار بين التفريغات والسجلّات وETW وTTD
TTD ليس علاجاً لكلّ شيء، ولا يحلّ محلّ الأدوات القائمة. هنا يُصفّ مقابل الأدوات المعالَجة في مقالاتنا.
| ما تريد معرفته | الأداة التي تستخدمها أوّلاً | متى يدخل TTD |
|---|---|---|
| الحالة في لحظة الانهيار | تفريغ الانهيار (WER LocalDumps / ProcDump) | عندما يُظهر التفريغ «إنّه فاسد» لا من أفسده |
| أيّ ملفّ أو مفتاح سجلّ فشل | Process Monitor | عندما تحتاج أيضاً كيف بُنيت الوسائط الممرَّرة إلى الواجهة الفاشلة |
| أداء الحاسوب كلّه طويل المدّة | WPR/WPA، PerfView (ETW) | عندما تكون مشكلة صحّة لا أداء وتحتاج ترتيباً على مستوى التعليمات |
| تسلسل الأحداث على مستوى الأعمال | سجلّات التطبيق | عندما حدث على مسار شيفرة بلا تسجيل (يسجّل TTD كلّ شيء بلا تغييرات شيفرة مسبقة1) |
| من كتب تلك القيمة، وترتيب الاستدعاءات | TTD | — |
flowchart TB
accTitle: اختيار طريقة تحقيق
accDescr: أوّلاً اقبض على الحالة بتفريغات وسجلّات خفيفة، وحقّق في الواجهات الفاشلة بـ ProcMon وفي الأداء بـ ETW، وانتقل إلى TTD فقط عندما ما زلت تحتاج من مرّر ماذا ومتى
start["العَرَض"] --> light["اقبض على الحالة بالتفريغات والسجلّات (خفيفة)"]
light --> api["واجهات فاشلة: ProcMon"]
light --> perf["الأداء: WPR/WPA، PerfView"]
light --> need{"تحتاج من ومتى وماذا مُرِّر؟"}
need -->|"نعم"| ttd["صمّم نطاق التسجيل وسجّل بـ TTD"]
need -->|"لا"| done["حُسم بالأدوات الخفيفة"]
الشكل 25: اقبض على «النتيجة» بأدوات خفيفة أوّلاً، وأخرج TTD فقط للحالات التي يتبيّن فيها أنّ «المسار» مطلوب.
الترتيب «الأخفّ أوّلاً». تكاد التفريغات والسجلّات لا تكلّف شيئاً للجمع، لذا أبقِها دائماً؛ أخرج TTD، بعد تصميم الفصل 5، للحالات التي أظهر فيها قراءة التفريغ أنّ المسار مطلوب. بالنظر إلى عبء TTD وإلى أنّ التتبّعات تحتوي معلومات سرّيّة، لا سبب لعكس هذا الترتيب.
11. ملاحظات تشغيليّة
- عامل التتبّعات كملفّات سرّيّة. يحتوي التسجيل محتويات الذاكرة ويمكن أن يشمل معلومات شخصيّة وأمنيّة مثل مسارات الملفّات وبيانات السجلّ ومحتويات الذاكرة والملفّات.12 عند التسجيل في بيئة عميل افهم مسبقاً ما قد يُشمَل (سلاسل الاتّصال والرموز وبيانات العملاء) وقرّر قناة نقل مشفَّرة وموضع تخزين ومدّة احتفاظ.
- شارك ملف
.runوحده. ملف.idxبحجم ملف.runتقريباً ويُولَّد تلقائيّاً عندما يفتحه WinDbg. تضغط ملفّات.runجيّداً. عند الإبلاغ عن خطأ في TTD نفسه ألحق ملف.outأيضاً.2 - أبقِ الإصدارات متوائمة. يواصل TTD التحديث مع WinDbg؛ يشمل 1.11.611 إصلاحاً لانهيارات التسجيل في برامج تستخدم AVX/AVX512 وتغيّراً في تنسيق الفهرس.11 خلط إصدار قديم في جانب التسجيل أو إعادة التشغيل يعني إعادة فهرسة أو إعادة تسجيل.
- راجع
-replayCpuSupportعند إعادة التشغيل على CPU مختلف. يفضّل الافتراضي قابليّة النقل، ويُوفَّرMostConservativeللحالات التي يختلف فيها CPU التسجيل عن CPU إعادة التشغيل (مثل إعادة تشغيل تتبّع Intel على arm64). بالمقابل، إذا علمت أنّ CPU إعادة التشغيل مساوٍ أو أفضل يمكنك اختيار تسجيلاً أصغر وأسرع.2 - يمكن تسجيل Windows Server أيضاً. يدعم TTD.exe Windows Server 2016/2019/2022/2025.2
- عزل فشل التسجيل. جرّب أوّلاً ما إذا أمكن تسجيل
ping.exeأوcmd.exe؛ إن لم يكن فاشتهِ تعارضاً مع برمجيّات تدخّليّة مثل مكافحة الفيروسات أو افتراضيّة التطبيقات.8
flowchart TB
accTitle: إجراء التعامل مع التتبّعات
accDescr: لأنّ التتبّع المسجَّل يحتوي محتويات الذاكرة افهم أيّ معلومات قد يحتوي، اضغط ملف .run وحده وسلّمه عبر قناة مشفَّرة، قرّر موضع التخزين ومدّة الاحتفاظ، وفي جانب التحليل افتحه بإصدار WinDbg نفسه وابنِ الفهرس
rec["اكتمل التسجيل (.run / .idx / .out)"] --> know["افهم أيّ معلومات قد يحتوي"]
know --> share["اضغط .run وحده، شفِّر، وسلّم"]
share --> keep["قرّر موضع التخزين ومدّة الاحتفاظ"]
keep --> open["افتح بإصدار WinDbg نفسه وولِّد .idx"]
الشكل 26: تسليم تتبّع تسليم ملفّ سرّي. قرّر الإجراء قبل أن تسجّل.
12. الخلاصة
- التفريغ «حالة»؛ TTD هو «المسار». في الحالات التي تحتاج أصل قيمة فاسدة أو ترتيب الاستدعاءات يحوّل TTD التحقيق إلى عمل آلي
- التسجيل أبطأ من 5 إلى 20 ضعفاً، وينمو بـ 5 إلى 50 ميغابايت في الثانية، ولا يستطيع الفصل. للتطبيقات طويلة التشغيل صمّم نطاق التسجيل أوّلاً بـ
-ring/-maxFileو-moduleو-recordmode Manualو-monitor - إعادة التشغيل «ادخل عبر حدث، وامشِ إلى الخلف»:
TTD.Eventsثمّ[Time Travel]ثمّt-/g-وba+g-لجعل المصحّح يجيب «من أفسده» TTD.CallsوTTD.Memoryاستعلامات على التتبّع كلّه. طابق طوابع السجلّ الزمنيّة بـToSystemTime()وSystemTimeStart- لـ .NET اقرأ الحالة بـ SOS بـ 64 بت واستخدم
TTD.Callsعند الحدود الأصليّة - التتبّع ملفّ سرّي. شارك ملف
.runوحده، وقرّر القناة والتخزين أوّلاً
الحالات التي جُمع فيها تفريغ لكن السبب خارج المتناول، أو توقّف العمل لأنّ الخطأ لا يتكرّر، تُحسَم في كثير من الأحيان بـ TTD ما إن يمكن تصميم نطاق التسجيل. يمكننا أيضاً تولّي كلّ شيء من تصميم التسجيل إلى تحليل ملف .run، لذا تواصل مع تفريغاتك وسجلّاتك.
مقالات ذات صلة
- قراءة ملفّات تفريغ الأعطال بـ WinDbg وSOS ── مدخل عمليّ إلى التحليل بعد الجمع
- مدخل إلى جمع dump انهيار تطبيقات Windows: WER / ProcDump / WinDbg
- PDB (قاعدة بيانات البرنامج) ما هي؟ ── فهم معلومات التصحيح والرموز (symbols) وSource Link
- تحقيق تعطّل الكاميرا الصناعيّة بعد تشغيل طويل: فصل handle leak
- ما الذي يعنيه «لا يستجيب» حقّاً ── كيف يقرِّر Windows أنّ تطبيقاً قد تجمّد، وكيف تصمِّم تطبيقات لا تتجمّد
- دليل عملي لـ Process Monitor (ProcMon) ── تحديد «الإعدادات لا تُقرأ» وACCESS DENIED خلال 10 دقائق
- WPR/WPA عمليّاً ── مدخل إلى تحقيق أداء المنظومة عند «الحاسوب كلّه بطيء»
- تحديد سبب «البطء» بـ PerfView وdotnet-trace ── مدخل عملي لتحقيق أداء .NET
- ما الذي يبقى بعد موت الأب ── إبقاء العمليّات الابن مقيدة بـ Job Objects
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع تحقيق السبب الجذري لأخطاء تطبيقات Windows التي تظهر فقط بعد تشغيل طويل أو بشكل متقطّع، بجمع تفريغات الانهيار والسجلّات وتتبعات TTD؛ وبناء إعداد تحقيق يشمل تصميم نطاق التسجيل؛ وعزل الإخفاقات التي تتورّط فيها حدود أصليّة (COM وP/Invoke ومجموعات تطوير الأجهزة). تواصل مبكّراً من مرحلة «لدينا تفريغ لكن لا نصل إلى السبب».
- التحقيق في الأخطاء وتحليل الأسباب الجذريّة
- الاستشارة التقنيّة ومراجعة التصميم
- تطوير تطبيقات Windows
- تواصل معنا
روابط مرجعيّة
-
Microsoft Learn, Time Travel Debugging - Overview. حول تسجيل TTD تنفيذ عمليّة وإعادة تشغيله أماماً وخلفاً، وميل التفريغات إلى تفويت الحالة ومسار التنفيذ اللذين أدّيا إلى الفشل، واشتراط التسجيل امتياز المسؤول، وإمكان احتواء التسجيلات معلومات شخصيّة وأمنيّة، وجدول مقارنة طرق التحقيق، وأدوار
.run/.idx، وتصحيح الشيفرة المُدارة بامتداد SOS في وضع 64 بت. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 -
Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. حول التباطؤ من 5 إلى 20 ضعفاً أو أكثر، والعجز عن فصل نفسه بعد الرفق، ودعم Windows Server 2016 إلى 2025، والتثبيت والنشر دون اتّصال، والأوضاع الثلاثة
-launch/-attach/-monitor، والخيارات-out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode، وقراءة ملف.out، ونصيحة مشاركة التتبّعات. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25 ↩26 ↩27 ↩28 ↩29 ↩30 ↩31 ↩32 ↩33 ↩34 -
Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. حول عدم التوافق مع برامج مكافحة الفيروسات ومراقبة الذاكرة وElectron، ووضع المستخدم فقط، وكون إعادة التشغيل للقراءة فقط، والعجز عن الحقن في العمليّات المحميّة (PPL)، وأثر الأداء بنحو 10 إلى 20 ضعفاً أثناء التسجيل. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Time Travel Debugging - Working with Trace Files. حول عوامل حجم التتبّع (بت واحد إلى بايت واحد لكلّ تعليمة)، والنمو بـ 5 إلى 50 ميغابايت في الثانية أثناء النشاط ولا شيء أثناء الخمول، وغياب سقف حجم أقصى، وكون الفهرس 1 إلى 2 ضعف التتبّع، وسلوك التسجيل والفهرسة عند نفاد القرص مع الحلّ. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. حول الإجراء العام للانتقال إلى موضع حدث استثناء والرجوع بـ
baوg-إلى الموضع الذي كُتبت فيه قيمة غير صالحة أخيراً، وكون نقطة الفشل غالباً داخل معالجة أخطاء عدّة خطوات بعد السبب الحقيقي، وإغلاق التتبّع عند انهيار وفهرسته تلقائيّاً من WinDbg، واستخدامTTD.Memoryو.Last(). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Microsoft Learn, !tt (time travel). حول تحديد المواضع لـ
!tt(نسبة مئويّة أوxx:yy)، ومعنى مكوّني الموضع (رقم التسلسل وعدد الخطوات)، وTTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, TTD Position Objects. حول
Percent/Sequence/Stepsلكائن Position، وSeekTo()، وToSystemTime()الذي يعيد زمن ساعة الحائط التقريبي (UTC)، وFFFFFFFFFFFFFFFE:0الذي يدلّ على نهاية التتبّع. ↩ ↩2 -
Microsoft Learn, Time Travel Debugging - Troubleshooting. حول اشتراط الارتفاع، وعدم دعم تسجيل إطلاق تطبيقات UWP، وخروج «العمليّات غير المعتادة» في جلسة أو سياق أمان آخر عن النطاق، والعزل بـ
ping.exe/cmd.exe، وتباطؤ إعادة التشغيل عند استخدام Application Verifier في الوقت نفسه، وإعادة بناء الفهرس بـ!index -status/!index -force. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Time Travel Debugging - Record a trace. حول التسجيل من Launch executable (advanced) / Attach to process في واجهة WinDbg، ومربع Record with Time Travel Debugging، وضبط موضع الحفظ بـ Configure and Record، وحصر الوحدات بـ Record subset of execution. ↩
-
Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). حول وثائق واجهة التسجيل داخل العمليّة التي، مجتمعة مع
-recordmode Manual، تتيح للبرنامج التحكّم ببدء التسجيل وإيقافه. ↩ -
Microsoft Learn, Time travel debugging release notes. حول إصلاح التسجيل لبرامج تستخدم AVX/AVX512 وتغيّر تنسيق الفهرس (يلزم إعادة الفهرسة) في 1.11.611، و
@$curframe.TTD.VariableHistory()المضاف في 1.11.553. ↩ ↩2 ↩3 -
Microsoft Learn, TTD Calls Objects. حول وسائط
TTD.Calls، والخصائصThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd، والافتراضيّات عند غياب معلومات رموز PDB (أربعة وسائط أعداد صحيحة 64 بت بلا إشارة،UnknownOrMissingSymbols)، واستغراق الحساب وقتاً مع تخزين النتائج مؤقّتاً. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Microsoft Learn, Time Travel Debugging - Replay a trace. حول التنفيذ العكسي بـ
p-/t-/g-، وتوقّفg-عند الأحداث نفسها للتنفيذ الأمامي، و!positions، وكون~sلا يغيّر الموضع في التتبّع. ↩ ↩2 ↩3 -
Microsoft Learn, TTD Event Objects. حول أنواع الأحداث (ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception) والكائنات الابن Position وModule وThread وException. ↩
-
Microsoft Learn, TTD Exception Objects. حول
Type(Software/Hardware) وProgramCounterوCodeوFlagsوPositionلكائن الاستثناء. ↩ -
Microsoft Learn, WinDbg: Timelines. حول نافذة Timelines التي تصوّر الاستثناءات ونقاط التوقّف ووصولات الذاكرة واستدعاءات الدوال، والنقر المزدوج على استثناء يصدر
Position.SeekTo(). ↩ -
Microsoft Learn, !positions. حول عرض كلّ خيط نشط وموضع كلّ خيط في التتبّع. ↩ ↩2
-
Microsoft Learn, Introduction to Time Travel Debugging objects. حول كائنات
@$curprocess.TTD/@$cursession.TTD، والاستعلامات بـOrderBy/Where/Select/GroupBy، وأمثلة تجميع أخطاءGetLastErrorوإيجاد آخر استدعاء لـMessageBoxW، ومعنىUnknownOrMissingSymbols، والأسباب الأربعة لعدم إرجاعCallsشيئاً. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, TTD Memory Objects. حول أنواع وصول
TTD.Memory(r/w/rw/e/rwe/ec) والانتقال إلى موضع عبر رابط[Time Travel]في النتائج. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
لماذا تتعطّل الوسائط ── قواعد وسائط سطر أوامر Windows
في Windows لا توجد مصفوفة وسائط؛ يصل إلى CreateProcess سلسلة واحدة ويتولّى الطرف المستقبل تقسيمها. نشرح قواعد CommandLineToArgvW وCRT و.N...
انتهاء خدمة برامج تشغيل الطابعات في Windows ── كيف تستعد تطبيقات الأعمال لطباعة التقارير والملصقات
تُنهى Microsoft تدريجيّاً خدمة برامج تشغيل الطابعات v3/v4، ومن تمّوز/يوليو 2026 يُفضَّل برنامج تشغيل فئة IPP. نرتّب في جداول قرار ما يختف...
ما الذي يبقى بعد موت الأب ── تربية العمليات الابنة في كائن Job
لماذا يبقى مساعد SDK ماسكاً الكاميرا أو منفذ COM بعد إنهاء الواجهة قسراً. كيف تجعل كائن Job شجرة العمليات وحدة واحدة وتصمم عمر العملية ال...
واجهة مجمع مؤشّرات الترابط Win32 ── تزامن دون إنشاء مؤشّرات ترابط، عبر CreateThreadpoolWork
أتستدعي CreateThread في كلّ مكان في الشيفرة الأصليّة؟ دليل من المصادر الأوّليّة لواجهة مجمع مؤشّرات الترابط Win32: كائنات work وtimer وwa...
الأنابيب المسمّاة عمليّاً ── IPC القياسيّ في Windows من التصميم إلى الأمان
دليل عمليّ للأنابيب المسمّاة، وسيلة IPC القياسيّة في Windows. يغطّي وضع البايت مقابل وضع الرسالة، وخوادم متعدّدة العملاء، وأمان ACL وانتح...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما الفرق بين Time Travel Debugging (TTD) وتفريغ الانهيار؟
- تفريغ الانهيار صورة لذاكرة لحظة الانهيار: يُظهر الحالة في تلك اللحظة، لا المسار الذي أدّى إليها. تتبّع TTD تسجيل كامل لتنفيذ تعليمات العمليّة يمكن إعادة تشغيله لاحقاً أماماً وخلفاً، لذا يمكنك الإرجاع والتحقّق مباشرة ممّن كتب المتغيّر أخيراً أو ما استُدعي قبل الاستثناء مباشرة. تنصّ وثائق Microsoft الرسميّة أيضاً على أنّ التفريغات تميل إلى تفويت الحالة ومسار التنفيذ اللذين أدّيا إلى الفشل. الثمن أنّ العمليّة تعمل أبطأ من 5 إلى 20 ضعفاً أثناء التسجيل، وينمو ملف التتبّع بنحو 5 إلى 50 ميغابايت في الثانية بينما العمليّة نشطة.
- هل يمكن ترك TTD معلّقاً على تطبيق إنتاج يعمل أيّاماً؟
- ليس كما هو. يبطئ TTD العمليّة كثيراً أثناء التسجيل، وينمو التتبّع بـ 5 إلى 50 ميغابايت في الثانية في عمليّة نشطة، ولا سقف لحجم الملفّ. استخدامه على تطبيق طويل التشغيل يفترض تصميم تسجيل: -ring (مخزن حلقي) في TTD.exe مع -maxFile لإبقاء آخر N ميغابايت فقط، و-module للتسجيل فقط أثناء تشغيل وحدتك، أو -recordmode Manual لترك التطبيق يختار فاصل التسجيل. كذلك، ما إن يُرفَق TTD لا يستطيع فصل نفسه، لذا يجب أن تقرّر مسبقاً كيف تنهي التسجيل، وهذا يعني إنهاء العمليّة (إعادة تشغيلها).
- هل يمكن تسجيل الخدمات والعمليّات في جلسات أخرى؟
- تصف وثائق TTD.exe -attach بأنّه موجَّه للتحقيق في الخدمات والتطبيقات طويلة التشغيل، و-monitor بأنّه يسجّل في كلّ مرّة يبدأ فيها برنامج أو خدمة. من جهة أخرى تنصّ صفحة استكشاف الأخطاء على أنّ العمليّات غير المعتادة التي تعمل في جلسة أخرى أو سياق أمان مختلف غير مدعومة حاليّاً للتسجيل. ما إذا كان يعمل فعلاً يعتمد على البيئة، لذا سجّل أوّلاً عمليّة بسيطة مثل ping.exe أو cmd.exe في التكوين نفسه للإنتاج، ثمّ جرّب العمليّة المستهدَفة، وبعد ذلك فقط أدخله في تشغيلك.
- هل يمكن استخدام TTD على تتبّعات تطبيقات .NET؟
- نعم. تنصّ الوثائق الرسميّة على أنّ امتداد SOS (sos.dll) العامل في وضع 64 بت يمكن استخدامه على تتبّع TTD في WinDbg لتصحيح الشيفرة المُدارة. النمط الأساسي تشغيل أوامر SOS مثل !clrstack و!pe عند كلّ موضع في التتبّع، بالانتقال إلى موضع حدث استثناء ثمّ قراءة المكدّس المُدار. يعتمد استعلام TTD.Calls، الذي يبحث عن الاستدعاءات باسم الرمز، على معلومات رموز PDB، لذا في تطبيقات .NET الاستخدام الموثوق تتبّع الاستدعاءات عبر الحدود الأصليّة مثل P/Invoke وCOM وواجهات Win32.
- هل من الآمن إرسال ملف تتبّع (.run) إلى شركة أخرى أو خارج المنظّمة؟
- ليس كما هو. يحتوي تسجيل TTD محتويات ذاكرة العمليّة، وتنصّ الوثائق الرسميّة صراحة على أنّه يمكن أن يشمل معلومات شخصيّة أو سرّيّة مثل مسارات الملفّات وبيانات السجلّ ومحتويات الذاكرة والملفّات. إذا أرسلت واحداً فافهم أوّلاً ما سُجِّل (سلاسل الاتّصال والرموز وبيانات العملاء وما شابه)، ثمّ قرّر قناة نقل مشفَّرة وموضع تخزين. يكفي مشاركة ملف .run وحده؛ يُولَّد ملف الفهرس (.idx) تلقائيّاً عندما يفتحه WinDbg.
- لا يعيد TTD.Calls شيئاً عندما أبحث عن دالّة. لماذا؟
- هناك أربعة أسباب رئيسة. أوّلاً الرموز: تُسمَّى الدوال في وحدة بلا PDB باسم UnknownOrMissingSymbols، وقد يكون اسم الوحدة بأحرف كبيرة، لذا راجع اسم الرمز الفعلي بأمر x. ثانياً قد لا تكون DLL المستهدَفة محمَّلة بعد في ذلك الموضع؛ انتقل إلى موضع بعد تحميل DLL واستعلم مجدّداً. ثالثاً إذا كانت الدالّة مضمَّنة لا يستطيع محرّك الاستعلام تتبّعها. رابعاً قد يكون البدل واسعاً جدّاً فيطابق دوالاً كثيرة؛ ضيّق النمط.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.