Time Travel Debugging — להקליט ולהריץ אחורה באגים שלא משתחררים באפליקציות ארוכות-ריצה

· עודכן בתאריך: · · WinDbg, Time Travel Debugging, Debugging, חקירת באגים, Windows, פיתוח Windows, .NET, C++, ייעוץ טכני

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 2 Sep 2026)
פרסום ראשון

“זה קורס פעם בחודש, רק באמצע הלילה.” “אותה פעולה אף פעם לא משתחררת אצלי.” “קיבלנו dump, אבל הסתכלות על אתר הקריסה לא אומרת למה הערך הפך למה שהוא.” בין חקירות הבאגים של אפליקציות Windows ארוכות-ריצה, אלה המקרים שצורכים הכי הרבה זמן. במאמר קודם, “קריאת crash dumps עם WinDbg + SOS,” הסתכלנו איך לקרוא crash dump, שהוא תצלום בודד. המאמר הזה ממשיך משם ומכסה את הכלי למצבים שבהם תצלום אינו מספיק: Time Travel Debugging (TTD).

TTD היא יכולת של WinDbg שמתעדת את כל הביצוע של process ומאפשרת replay מאוחר יותר, קדימה ואחורה. במקום לנסות שוב ושוב לשחזר באג, אפשר “להריץ אחורה” את session הדיבאגר.1 הקוראים המיועדים הם מפתחים ומתחזקים של אפליקציות Windows, .NET ו-C++ שכבר עשו את חלקם בחקירת dump ולוגים ועדיין יש להם מקרים שאינם מגיעים לשורש. סביבת התנאי המוקדם היא Windows 10/11 או Windows Server 2016 ואילך עם WinDbg הנוכחי, וההקלטה דורשת הרשאות Administrator.12 רמת הקושי היא ביניים.

הנחות המאמר

פריט תוכן
קוראים מיועדים מפתחים ומתחזקים של אפליקציות Windows עם באגים ארוכי-ריצה או לסירוגין ש-dumps ולוגים אינם מגיעים לשורש שלהם
ידע מוקדם ניסיון בפתיחת dump ב-WinDbg והרצת !analyze -v או !clrstack. תוכן מאמר אנליזת SOS מונח
סביבת תנאי מוקדם Windows 10/11 או Windows Server 2016/2019/2022/2025, WinDbg (גרסה נוכחית), TTD.exe, הרשאות Administrator2
מחוץ להיקף שילוב עם Visual Studio Enterprise Snapshot Debugger; kernel mode (TTD הוא user mode בלבד3)

1. קודם המסקנה

  • Dump שומר “מצב”; TTD שומר את “הנתיב.” התיעוד הרשמי מציין ש-dumps נוטים להחמיץ את המצב ואת נתיב הביצוע שהובילו לכשל.1 אם תצלום של רגע הקריסה אינו אומר את הסיבה, מה שצריך אחר כך אינו עוד תצלומים אלא הקלטה.
  • ההקלטה כבדה. בזמן הקלטה, תהליך היעד רץ פי 5 עד 20 לאט יותר או גרוע מזה, וקובץ ה-trace גדל ב-5 עד 50 MB לשנייה כשהתהליך פעיל, בלי תקרה.24 זה אינו כלי שמחברים ללא תנאי לאפליקציה ארוכת-ריצה.
  • לאפליקציות ארוכות-ריצה, מתכננים “מה להקליט.” פרק 5 מכסה את ארבע נקודות הכניסה ש-TTD.exe מספק: -ring/-maxFile (לשמור רק את N ה-MB האחרונים), -module (להקליט רק בזמן שמודול שלכם רץ), -recordmode Manual (לתת לאפליקציה לציין את מרווח ההקלטה), ו--monitor (להקליט כל הפעלה).2
  • Replay סובב סביב שלושה דברים: positions (!tt), events (dx @$curprocess.TTD.Events), ושאילתות (TTD.Calls / TTD.Memory). משלבים ba (breakpoint מסוג break-on-access) עם g- (ביצוע לאחור), והדיבאגר עונה ישירות “מי כתב אחרון את הערך הזה?”5
  • Trace מכיל את תוכן הזיכרון. הוא יכול לכלול מידע אישי וסודי כמו נתיבי קבצים, נתוני Registry ותוכן של זיכרון וקבצים.1 מתכננים שיתוף ואחסון כמו לקובץ סודי.
  • יודעים מה TTD אינו יכול לפני שמתחילים. הוא אינו יכול להקליט kernel mode, אינו יכול להזריק ל-protected processes (PPL), אינו יכול לנתק את עצמו אחרי attach, ואי אפשר לשנות זיכרון בזמן replay.32

ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 32, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle

2. איפה dump אינו מספיק

Crash dump הוא העתק של זיכרון ו-registers ברגע שהתהליך קרס (או נעצר). כפי שמתואר במאמר אנליזת SOS, !clrstack אומר איפה זה קרס ו-!dumpheap -stat אומר מה צורך את ה-heap. מה שהוא אינו יכול להגיד הוא מה קרה בדרך למצב הזה.

מה dump לוכד ומה TTD לוכדCrash dump לוכד רק את המצב ברגע הקריסה, לא את הנתיב שהוביל לשם. TTD שומר את כל ביצוע ההוראות מתחילת ההקלטה עד הסוף, כך שהנתיב נשמר יחד עם המצבCrash dump: מצב ברגע הקריסהלא מראה למה הערך הפך למה שהואTTD trace: ביצוע הוראות של מרווח ההקלטהאפשר לחזור ל-position שבו הערך נכתבהפער הזה הוא מה שגורר חקירות ארוכות-ריצה

איור 1: Dump הוא מצב; TTD הוא הנתיב. בבאגים ארוכי-ריצה, מה שרוצים בדרך כלל הוא האחרון.

מקרים טיפוסיים נראים כך.

  • שדה אחד של מבנה ב-heap מחזיק ערך בלתי אפשרי. ה-dump מראה את הערך המשובש, אבל לא מי כתב אותו או מתי.
  • מיקום ה-exception ידוע, אבל לא למה הארגומנט שהועבר אליו היה לא תקין. כשעולים בקוראים, הפונקציה שייצרה את הערך בדרך כבר אינה ב-stack.
  • Handles או זיכרון גדלים לאורך חודש. Dump בודד יכול רק לומר “זה גדל”; נתיב הקריאה שהגדיל אותו אינו שם.
מה dump מחמיץ במקרים טיפוסיים ארוכי-ריצההערך המשובש נלכד אבל לא מי כתב אותו, מיקום ה-exception נלכד אבל הפונקציה שייצרה את הארגומנט כבר אינה ב-stack, וגידול המשאב נלכד אבל לא הנתיב שהגדיל אותו; שלושת סוגי המקרה חולקים את זהשדה משובשאין רישום מי כתב אותו או מתיException על ארגומנט לא תקיןהפונקציה שייצרה את הערך כבר אינה ב-stackמשאב שגדל לאורך חודשאין רישום של נתיב הקריאה שהגדיל אותונקודה משותפת: יש מצב, אבל אין נתיב

איור 2: לכל שלושת המקרים יש “מצב אבל אין נתיב.” עוד תצלומים אינם ממלאים את הנתיב.

התיעוד של Microsoft משווה את החוזקות והחולשות של שיטות החקירה כך.1

שיטה חוזקות חולשות
Live debugging אינטראקטיבי, מראה את זרימת הביצוע, ומאפשר לשנות מצב עוצר את עבודת המשתמש. דורש מאמץ לשחזר שוב ושוב. לעתים קרובות לא שמיש בייצור. קשה לחזור מנקודת הכשל לסיבה
Dumps אין צורך בשינויי קוד מראש. חודרנות נמוכה, אפשר לאסוף ב-trigger. overhead כמעט אפס כשלא בשימוש גם עם snapshots רצופים, התצוגה של “זמן שחלף” גסה
Telemetry ולוגים קל. קשור לתרחישים עסקיים אין לוגים בנתיבי קוד בלתי צפויים. עומק נתונים לא מספיק, ומוטמע סטטית בקוד
TTD חזק בבאגים מורכבים. אין צורך בשינויי קוד מראש. אפשר replay offline כמה פעמים ומתעד הכול overhead גדול בזמן הקלטה. עשוי לאסוף יותר נתונים מהנחוץ. קבצים גדלים

יש עוד מאפיין חשוב שה-walkthrough הרשמי של TTD מצביע עליו. כשהדיבאגר נעצר בנקודת הכשל, הנקודה הזו לעתים קרובות נמצאת בתוך קוד טיפול בשגיאות כמה צעדים אחרי הסיבה האמיתית.5 Dump תמיד נלקח ב-position “כמה צעדים אחר כך” הזה. עם TTD אפשר ללכת אחורה משם הוראה אחת בכל פעם.

הפער בין נקודת הכשל לסיבה האמיתיתנקודת הכשל שבה נלקח dump לעתים קרובות נמצאת בתוך טיפול בשגיאות כמה צעדים אחרי הסיבה האמיתית, ועם TTD אפשר להריץ אחורה מהנקודה הזו הוראה-הוראה בחזרה לסיבהDumpTTDסיבה אמיתית (ההוראה ששיבשה את הערך)כמה צעדים קדימהנקודת כשל (exception, טיפול בשגיאה)קפוא כאןחוזרים עם p- / t- / g-

איור 3: Dump קפוא בנקודת הכשל. TTD יכול ללכת אחורה מנקודת הכשל לסיבה.

3. איך TTD עובד ומה הוא עולה

3.1 מה מוקלט

TTD מזריק מנוע הקלטה לתהליך היעד ומתעד את ההוראות שבוצעו, הוראה-הוראה. במילות התיעוד הרשמי, הוא “מקודד trace ברמת הוראה מלא בערך פחות מבית אחד להוראה”; בפועל זה נוחת איפשהו בין bit אחד לבית אחד להוראה. תוכניות שמבצעות מעט סוגי פונקציות ומטפלות במעט נתונים מייצרות traces קטנים יותר; ההפך מייצר גדולים יותר.24

הקלטה מייצרת שני קבצים.1

קובץ תפקיד גודל משוער
.run ה-trace עצמו. שומר את ביצוע ההוראות בזמן ההקלטה גדל ב-5 עד 50 MB לשנייה כשפעיל. אינו גדל כש-idle4
.idx ה-index. נתוני עזר שמאפשרים ל-WinDbg replay ושאילתות זיכרון ביעילות. נוצר כשההקלטה נעצרת, וגם נוצר אוטומטית כש-WinDbg פותח את קובץ .run פי 1 עד 2 מגודל ה-trace4
הזרימה מהקלטת TTD ל-replayמנוע הקלטה מוזרק לתהליך היעד וביצוע הוראות נרשם לקובץ .run; כש-WinDbg פותח את קובץ .run הוא יוצר index מסוג .idx ומריץ קדימה ואחורה באמצעות positions, events ושאילתותתהליך יעדמנוע הקלטה מוזרק (TTDRecordCPU).run (רישום ביצוע הוראות)פתיחה ב-WinDbgיצירת .idx (index)Replay קדימה ואחורה עם positions, events ושאילתות

איור 4: ההקלטה נעשית על ידי TTD.exe או WinDbg; הקריאה נעשית על ידי WinDbg. שיתוף רק של קובץ .run מספיק.

הזמן בתוך קובץ .run מבוטא כ-“position.” הוא לובש צורה של שני מספרים הקסדצימליים מופרדים בנקודתיים, כמו 12:0 או 1A0:12F; החצי הראשון הוא מספר הרצף (שמתאים לאירוע רצף) והחצי השני הוא מספר ההוראות המשוער מאז אותו אירוע.6 FFFFFFFFFFFFFFFE:0 פירושו סוף ה-trace.7 Positions תופסים את הבמה בפרק 6.

איך positions ב-trace מבוטאיםPosition הוא מספר רצף הקסדצימלי וספירת צעדים מופרדים בנקודתיים; ההתחלה קרובה ל-0, הסוף מבוטא כ-FFFFFFFFFFFFFFFE:0, ואפשר גם לעבור ל-position משוער לפי אחוזPosition xx:yy (הקסדצימלי)xx: מספר רצףyy: הוראות מאז אותו אירועהסוף הוא FFFFFFFFFFFFFFFE:0אחוזים כמו !tt 50 גם עובדים

איור 5: Position הוא “מספר אירוע:ספירת הוראות.” זה אינו זמן שעון קיר, אבל אפשר להמיר לזמן שעון קיר כפי שמוצג בפרק 6.

3.2 העלות

התיעוד הרשמי עצמו מתאר TTD כ-“invasive technology.”2 הנה העלות במספרים.

פריט תוכן
מהירות תהליך היעד רץ פי 5 עד 20 לאט יותר או גרוע מזה בזמן הקלטה (תלוי באפליקציה ובאפשרויות ההקלטה). זה עשוי לא להיות מורגש ב-UI, אבל זה מורגש בפעולות כבדות כמו דיאלוג Open File23
גידול קובץ 5 עד 50 MB לשנייה כשפעיל. כמה דקות הקלטה יכולות להגיע לכמה GB. אין תקרה4
דיסק נגמר אם הדיסק מתמלא בזמן הקלטה, TTD כותב את העמוד האחרון ואז למעשה ממתין עד שיוכל לכתוב שוב. WinDbg ממשיך להציג את דיאלוג ההקלטה ואינו פולט לא שגיאה ולא אזהרה. התוצאה היא trace לא שלם4
זיכרון ההקלטה מוסיפה את ה-overhead של virtual CPUs לזיכרון תהליך היעד (ברירת מחדל 55 ב-x64/ARM64, 32 ב-x86). מקטינים עם -numVCpu רק כשחסר זיכרון2
לא ניתן לנתק אחרי attach, TTD אינו יכול לנתק את עצמו. כשמסיימים הקלטה, סוגרים את האפליקציה או מסיימים את התהליך. אם התהליך חיוני למערכת, צריך reboot ל-OS2
עלות ההקלטה והשפעתה על אפליקציות ארוכות-ריצהההאטה בזמן הקלטה, גידול הקובץ, ההמתנה השקטה כשהדיסק נגמר, ואי-היכולת לנתק אחרי attach הם ארבע העלויות שהופכות לבלתי אפשרי לחבר TTD ללא תנאי לאפליקציה ארוכת-ריצההקלטת TTDפי 5 עד 20 לאט יותרגדל 5 עד 50 MB לשנייה, אין תקרהממתין בשקט כשהדיסק נגמרלא ניתן לנתק אחרי attachהקלטה רציפה ללא תנאי אינה מעשית

איור 6: כל אחת מארבע העלויות שוללת הקלטה רציפה. לכן נחוץ “תכנון ההקלטה” של פרק 5.

3.3 מה הוא אינו יכול

  • User mode בלבד. אפשר להקליט רק את הביצוע ב-user mode של process; קוד שרץ ב-kernel mode, כמו drivers, אינו ניתן לדיבוג.3
  • Protected processes. TTD אינו יכול להזריק את עצמו ל-Windows protected processes כמו Protected Process Light (PPL).3
  • Replay הוא לקריאה בלבד. אפשר לחזור בזמן, אבל אי אפשר לשנות היסטוריה. פקודות שקוראות זיכרון עובדות; פקודות שמשנות אותו לא.3
  • אי-תאימות עם אנטי-וירוס ותוכנות ניטור זיכרון. כי TTD עושה hook לתהליך, הוא מתנגש עם תוכנה שעוקבת או מצלה קריאות זיכרון מערכת. אם ההקלטה מייצרת שגיאה שנראית כמו הרשאות לא מספיקות, מכבים זמנית תוכנה כזו כדי לבודד. גם Electron framework הוא התנגשות ידועה; גם אם הקלטה מצליחה, תהליך היעד עלול deadlock או לקרוס.3
  • אפליקציות UWP אינן ניתנות להפעלה ולהקלטה (attach לאפליקציית UWP שכבר רצה אפשרי). גם “תהליכים חריגים” שרצים ב-session אחר או בהקשר אבטחה שונה אינם נתמכים כרגע.8
מה TTD אינו יכול להקליט או לעשותקוד kernel-mode, protected processes, הקלטת הפעלה של אפליקציות UWP, ותהליכים ב-session או הקשר אבטחה אחר אינם ניתנים להקלטה; אי אפשר לשנות זיכרון בזמן replay; ואנטי-וירוס ו-Electron יכולים להתנגשמגבלות TTDלא ניתן להקליטמגבלות והתנגשויותקוד kernel-mode (drivers וכו')Protected processes (PPL)הקלטת הפעלה של UWP (attach אפשרי)Sessions והקשרי אבטחה אחריםReplay לקריאה בלבדעשוי להתנגש עם אנטי-וירוס ו-Electron

איור 7: המגבלות באות בשני סוגים: “לא ניתן להקליט” ו-“התנגשויות.” האחרון צריך בידוד לפי סביבה.

הפריט האחרון מכשיל כל מי שרוצה להקליט service. התיעוד של TTD.exe מתאר -attach כמיועד ל-“חקירת services ואפליקציות ארוכות-ריצה” ו--monitor כמקליט “בכל פעם שתוכנית או service עולה”,2 מה שאינו מסתדר פשוט עם האמירה בדף ה-troubleshooting. בפועל, הגישה הבטוחה היא לאמת לפי סביבה בסדר הזה: מאשרים שאפשר להקליט ping.exe או cmd.exe באותה תצורה כמו הייצור, ואז מנסים את תהליך היעד.8

4. הקלטה — ה-UI של WinDbg ו-TTD.exe

יש שתי נקודות כניסה להקלטה: ה-UI של WinDbg, או TTD.exe בשורת הפקודה.

4.1 הקלטה מ-UI של WinDbg

מריצים WinDbg כמנהל (elevation חובה ל-TTD1), בוחרים File > Start debugging > Launch executable (advanced), מציינים את קובץ ההרצה, ומסמנים Record with Time Travel Debugging. בחירת Configure and Record מאפשרת להגדיר את מיקום קובץ ה-trace ו-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”. כשהאפליקציה יוצאת (או קורסת), ה-trace נסגר ו-WinDbg פותח אותו אוטומטית ובונה את ה-index.5

זרימת ההקלטה מ-UI של WinDbgב-WinDbg שהופעל כמנהל, בוחרים Launch executable (advanced) או Attach to process, מסמנים Record with Time Travel Debugging, מגדירים מיקום שמירה וסינון מודול ב-Configure and Record, עוברים בדיאלוג ההקלטה, וכשהאפליקציה יוצאת ה-trace נסגר ומאונדקס אוטומטיתמפעילים WinDbg כמנהלLaunch executable (advanced) / Attach to processמסמנים Record with Time Travel DebuggingConfigure and Record: מיקום שמירה, סינון מודולדיאלוג הקלטה (Stop and Debug)האפליקציה יוצאת, ה-trace נסגר ומאונדקס אוטומטית

איור 8: הקלטה מה-UI לוקחת חמישה שלבים. מדלגים על “הפעלה כמנהל” ונעצרים בדיאלוג הראשון.

4.2 הקלטה עם TTD.exe

כשצריך להקליט במחשב שבו אי אפשר להתקין WinDbg, או לאוטומציה של הקלטה, משתמשים ב-TTD.exe לבדו. הוא מותקן דרך App Installer מ-https://aka.ms/ttd/download, ואחרי ההתקנה אפשר לאמת עם ttd.exe -help. לסביבות offline, Microsoft גם מספקת נוהל רשמי (עם סקריפט PowerShell) לפירוק חבילת MSIX ידנית וחילוץ רק הבינאריים.2 ההקלטה דורשת הרשאות Administrator ובדרך כלל רצה מ-command prompt של מנהל.2

יש שלושה מצבי הקלטה.2

שלושת מצבי ההקלטה של TTD.exelaunch מפעיל תהליך חדש עם ארגומנטים ומקליט אותו, אבל הוא רץ עם הרשאות מוגבהות. attach מתחבר לתהליך רץ לפי PID. monitor מקליט בכל פעם שהתוכנית שצוינה עולה, וההפעלה קורה עם הרשאות רגילותמצבי הקלטה של TTD.exe-launch: הפעלה והקלטה-attach: attach ל-PID רץ-monitor: הקלטה בכל הפעלהמופעל עם הרשאות Administratorשומר הרשאות רגילותנתיב הפעלה רגיל, מתאים לאוטומציה

איור 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 הוא המצב היחיד שיכול להעביר ארגומנטים, אבל התוכנית מופעלת עם אותן הרשאות (Administrator) כמו TTD.exe. לאפליקציות שההתנהגות שלהן משתנה עם הרשאות, מקליטים עם -attach או -monitor כדי לשמור הרשאות רגילות.2
  • -attach דורש שתיקיית הפלט כבר קיימת. אם מציינים שם קובץ, לא יכול להיות קובץ בשם הזה.2
  • -monitor מתקין driver לניטור הפעלת תהליכים ומקליט בכל פעם שהתוכנית שצוינה (אפשר יותר מאחת) עולה. הוא נשאר בתוקף עד reboot ונעצר עם Ctrl+C. היתרונות הם שאין צורך להרכיב את ההפעלה בעצמכם, היעד רץ עם הרשאות רגילות, והוא מתאים לאוטומציה בסקריפט. הוספת -cmdLineFilter "string" מקליטה רק הפעלות ששורת הפקודה שלהן מכילה את המחרוזת הזו.2
  • -children מקליט גם תהליכי בן, אבל כל תהליך מקבל קובץ .run משלו, ו-WinDbg יכול לפתוח רק אחד בכל פעם.2

בזמן הקלטה מופיע UI קטן עם שני כפתורים: “Tracing Off” (לעצור הקלטה ולתת לאפליקציה להמשיך) ו-“Exit App” (לסגור את האפליקציה ולסיים הקלטה). לאוטומציה, מסתירים אותו עם -noUI ומקבלים את ה-EULA עם -accepteula.2 לוג ההקלטה נשמר בקובץ .out באותו מיקום כמו קובץ .run, שבו אפשר לקרוא את זמני שעון הקיר של התחלה וסיום ההקלטה, אורך session ההקלטה (זמן סימולציה), האם זה היה launch או attach, וגרסת ה-OS. כשהקלטה נכשלת, חלק מהודעות השגיאה מופיעות רק בקובץ .out.2

5. תכנון הקלטה לאפליקציות ארוכות-ריצה

זה לב המאמר. בהתחשב בעלויות של פרק 3, לתפוס באג שמתרחש בזמן לא ידוע בתהליך שרץ ימים דורש תכנון הקלטה. TTD.exe מספק ארבעה אמצעים, ובוחרים לפי אופי התסמין.

בחירת היקף ההקלטה לפי תסמין של אפליקציה ארוכת-ריצהאם לא יודעים מתי זה יקרה, שומרים רק את החלק האחרון עם ring buffer; אם יודעים איזה מודול חשוד, מקליטים רק את המודול הזה; אם אפשר לשנות את האפליקציה, מציינים את המרווח עם API ההקלטה הידנית; אם זה מופיע רק באתחול או בהפעלות מסוימות, מקליטים כל הפעלה עם monitor modeלא ידוע מתי זה קורהרק באתחול או בהפעלות מסוימותמודול חשוד ברוראפשר לשנות את האפליקציהאופי התסמין?-ring / -maxFile: שומרים רק את הסוף-monitor: מקליטים כל הפעלהמוסיפים -moduleמציינים את המרווח עם -recordmode Manual

איור 10: הארבעה אינם בלעדיים זה לזה. שילוב -ring עם -module הוא השילוב המעשי ביותר לאפליקציות ארוכות-ריצה.

5.1 Ring Buffer — שומרים רק את N ה-MB האחרונים

עם -ring, ה-trace נכתב ל-ring buffer בגודל שניתן ב--maxFile, והקובץ לעולם אינו גדל מעבר למגבלה הזו. מה שנשאר הוא רק החלק האחרון של ההקלטה שמתאים לגודל הזה.2 היחידה של -maxFile היא MB; במצב ring-buffer ברירת המחדל היא 2,048 MB, המינימום 1 MB, והמקסימום 32,768 MB (ברירת המחדל ל-ring בזיכרון של תהליך 32-bit היא 256 MB).2

:: מצמידים לאפליקציית ניטור שרצה ומשאירים רק את 4 GB האחרונים של הביצוע
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run

:: כשמזהים את התסמין, עוצרים את ההקלטה (האפליקציה ממשיכה לרוץ)
TTD.exe -stop 21440

-stop מקבל שם תהליך, PID, או all, ועוצר את ההקלטה הזו. -wait <seconds> ממתין עד שכל session הקלטה במערכת הסתיים (-1 ללא הגבלה), ומשמש בסקריפטי אוטומציה לאכיפת הסדר “עוצרים, ואז אוספים את הקובץ.”2

ציר זמן של הקלטת ring-bufferההקלטה מתחילה ב-attach, חלקים ישנים נדחפים החוצה מה-ring buffer, וכשהתסמין מופיע ומוציאים stop, נשאר רק ה-maxFile האחרון כ-traceמתחילים הקלטה עם -attach -ringמרווחים ישנים נדחפים החוצההתסמין מופיעעוצרים הקלטה עם -stopנשאר רק ה-maxFile האחרוןמגדירים גודל כך שהרגעים לפני התסמין ייכנסו ל-buffer

איור 11: Ring buffer הוא אמצעי לשמור “את הרגעים לפני התסמין.” עובדים את -maxFile אחורה מ-“זמן מהבחנת התסמין עד עצירה, כפול גידול לשנייה.”

יש שתי נקודות תכנון.

  1. עושים את ה-buffer גדול מספיק כדי לספוג את הזמן מהבחנה עד עצירה. בתהליך פעיל ה-trace גדל ב-5 עד 50 MB לשנייה,4 כך ש-ring של 4 GB מתאים לדקה עד שתיים תחת עומס כבד ולקצת יותר מעשר דקות תחת עומס קל. צריך מנגנון ששומר את הפיגור בין זיהוי התסמין (שורה ספציפית בלוג, סף מונה, התראה מניטור) לבין -stop בתוך החלון הזה.
  2. עצירה אינה ניתוק. -stop עוצר את ההקלטה, אבל TTD אינו מנתק את עצמו מתהליך היעד.2 סיום ההקלטה לגמרי דורש סיום התהליך, לכן מכניסים “restart בחלון התחזוקה הבא אחרי איסוף ה-trace” לנוהל התפעול.
עצירת הקלטת ring-buffer בתיאום עם ניטורכשצד הניטור מזהה את התסמין דרך לוגים או מונים, הוא קורא ל-TTD.exe stop, אוסף את קובץ .run הסופי, ומפעיל מחדש את התהליך בחלון התחזוקה הבא כדי להסיר TTDתהליך היעדTTD.exeניטור (לוגים, מונים)תהליך היעדTTD.exeניטור (לוגים, מונים)מזהים את התסמין-stop PIDעוצרים הקלטה (התהליך ממשיך).run מוכןאוספים .run, מצפינים, ושומריםמפעילים מחדש בחלון התחזוקה הבא

איור 12: רק כשהפיגור בין זיהוי ל--stop נכנס ל-buffer נשארים הרגעים לפני התסמין ב-trace. ה-restart הוא חלק מהתפעול.

5.2 סינון מודול — מקליטים רק בזמן שקוד שלכם רץ

-module <module name> מקליט רק את המודול שצוין (קובץ ההרצה עצמו או DLL טעון; אפשר יותר מאחד) ואת הקוד שהמודול הזה קורא. תהליך היעד רץ במהירות מלאה עד שקוד במודול שצוין מתבצע; ההקלטה מתחילה כשהביצוע נכנס למודול, נעצרת כשהוא יוצא, והתהליך חוזר למהירות מלאה. כי הדלקה וכיבוי של הקלטה יקרים, ההקלטה נשארת דולקת כל עוד המודול שצוין קורא למודולים אחרים בתהליך.2

:: מקליטים רק בזמן ש-DLL של לוגיקת המדידה שלנו רץ (בשילוב עם ring)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\

Trace שנעשה כך פשוט מדלג על המרווחים שבהם ההקלטה הייתה כבויה, ומתייחס ל-“ההוראה הבאה” כהוראה הראשונה אחרי שההקלטה חודשה, ומדבגים אותו לא אחרת מ-trace של כל התהליך.2 באפליקציות ארוכות-ריצה, לולאת idle של ה-UI והעיבוד הפנימי של ה-framework נוטים להסביר את רוב ההוראות שבוצעו, כך שהגבלת ההקלטה למודול שלכם לבדו מפחיתה overhead וגודל קובץ משמעותית.

איך הקלטה מסוננת לפי מודול מתנהגתתהליך היעד רץ במהירות מלאה מחוץ למודול שצוין; ההקלטה מתחילה כשנכנסים לקוד במודול שצוין, ממשיכה בזמן שהמודול הזה קורא למודולים אחרים, ונעצרת כשהביצוע יוצא מהמודולמחוץ למודול שצוין: מהירות מלאהנכנסים למודול שצוין: ההקלטה מתחילהמודולים אחרים שהוא קורא: ההקלטה ממשיכהיוצאים מהמודול שצוין: ההקלטה נעצרת

איור 13: כי גם Win32 APIs וה-runtime שה-DLL שלכם קורא מוקלטים, זה מספיק כדי לעקוב אחרי “מה הקוד שלנו מסר ל-OS.”

5.3 הקלטה ידנית — נותנים לאפליקציה לציין את המרווח

עם -recordmode Manual, התהליך ממשיך לרוץ במהירות מלאה גם אחרי ש-TTD מוזרק, וההקלטה קורה רק כשהתוכנית קוראת ל-API ההקלטה הפנימי של TTD (ברירת המחדל, Automatic, מקליטה מרגע ההזרקה).2 תיעוד ה-API והדוגמאות נמצאים במאגר WinDbg-Samples ב-GitHub.10

אם אפשר לשנות את האפליקציה, זו הגישה הכי פחות בזבזנית. אפשר לבנות לוגיקה שמתחילה הקלטה כשהאפליקציה עצמה יודעת שחריגה קרובה ועוצרת כשהדברים חוזרים לנורמה: “שלושה ניסיונות תקשורת רצופים נכשלו,” “הצטברות התור עברה את הסף,” וכן הלאה. בתצורת תהליך מפקח כמו זו שמכוסה במאמר Job Object, המפקח יכול להחליט על המרווח במקום.

5.4 Monitor Mode — מקליטים כל הפעלה

לבאגים שמופיעים רק מיד אחרי האתחול או בהפעלות מסוימות, -monitor מתאים. גם טבלת ההשוואה הרשמית מתארת monitor mode כמיועד ל-“תפיסת בעיות לסירוגין ובעיות אתחול.”2 כשמקליטים את אותה תוכנית פעמים רבות, שמות הקבצים הרציפים ברירת המחדל (MyApp01.run, MyApp02.run וכן הלאה) נהיים לא יעילים כי צריך לסרוק קבצים קיימים, לכן משתמשים ב--timestampFilename לשמות עם חותמת זמן. אפשר להגביל את מספר ההקלטות הסימולטניות עם -maxConcurrentRecordings.2

איך monitor mode מתנהגאפשרות monitor מתקינה driver לניטור הפעלת תהליכים, מצמצמת את היעד עם סינון שורת הפקודה בכל פעם שהתוכנית שצוינה עולה, מקליטה אותה, יוצרת קובץ trace נפרד לכל הפעלה, וממשיכה עד Ctrl+C או rebootכןלאמתקינים את driver ניטור ההפעלהמזהים הפעלה של התוכנית שצוינהמתאים ל-cmdLineFilter?מקליטים את ההפעלה הזו (קובץ נפרד לכל הפעלה)לא מקליטיםמחכים להפעלה הבאה (עד Ctrl+C או reboot)

איור 14: Monitor mode “אורב להפעלה.” הוא מתאים לבאגים שמופיעים בכל הפעלה ולבאגים שאפשר לצמצם לפי תנאי הפעלה.

5.5 איפה לשים את הדיסק

להקלטות ארוכות-ריצה, שמים את ה-traces ב-volume ייעודי ומכניסים את גידול קובץ .run לניטור. כפי שצוין בסעיף 3.2, כשהדיסק מתמלא, ההקלטה פשוט ממתינה בשקט בלי שגיאה. ה-workaround הרשמי פרימיטיבי באותה מידה: “בודקים מקום פנוי ב-Explorer” ו-“בודקים שקובץ .run גדל באופן סדיר.”4 בלי ניטור מקום פנוי, נשארים עם trace לא שלם שבו הרגע שרציתם ביותר מעולם לא נכתב.

איך דיסק שנגמר מייצר trace לא שלםכשהדיסק נגמר בזמן הקלטה, TTD כותב את העמוד האחרון וממתין בשקט בלי שגיאה ובלי אזהרה, כך שהתסמין שקורה אחר כך אינו נרשם, ומשאיר trace לא שלם שנפתח אבל חסר בו החלק הקריטי. מונעים עם volume ייעודי וניטור גידול .runמונעהדיסק נגמרכותב את העמוד האחרון וממתין בשקטאין שגיאה, אין אזהרההתסמין קורה אחר כךTrace לא שלם בלי התסמיןVolume ייעודי + ניטור גידול .run

איור 15: Trace ש”נפתח אבל חסר בו החלק הקריטי” נולד מפער בניטור דיסק.

6. Replay — Positions, Events והרצה אחורה

6.1 פתיחה

כשפותחים קובץ .run ב-WinDbg, אם אין index, !index רץ אוטומטית ובונה את קובץ .idx תוך ספירת keyframes (positions ב-trace שנוצרים אוטומטית לאינדוקס; traces גדולים יותר יש להם יותר). ככל שה-trace גדול יותר, זה לוקח יותר זמן.5 את מצב ה-index אפשר לבדוק עם !index -status; אם הוא מדווח משהו שאינו “Index file loaded,” בונים מחדש עם !index -force. אם זה עדיין נכשל, סוגרים את הדיבאגר, מוחקים את קובץ .idx, ופותחים מחדש את קובץ .run. בניית index מחדש אינה משנה את קובץ .run, כך שנתונים אינם אובדים.8

סייג אחד: כשאינדוקס של traces גדולים שופר ב-TTD 1.11.611, פורמט ה-index השתנה, וtraces קיימים צריכים אינדוקס מחדש.11 אם סוחבים קבצי .idx ישנים, כאן נכשלים.

בדיקה ובנייה מחדש של ה-indexאחרי פתיחת ה-trace, בודקים את המצב עם !index -status; אם זה משהו שאינו Index file loaded, בונים מחדש עם !index -force, ואם זה עדיין נכשל, סוגרים את הדיבאגר, מוחקים את קובץ .idx, ופותחים מחדש את קובץ .run. בנייה מחדש אינה משנה את קובץ .runIndex file loadedכל דבר אחרנכשלמצליחפותחים את ה-trace!index -statusממשיכים לאנליזהבונים מחדש עם !index -forceסוגרים, מוחקים .idx, פותחים מחדש .run

איור 16: בניית index מחדש אינה נוגעת בקובץ .run. כשיש ספק, מוחקים ופותחים מחדש.

6.2 תנועה לפי Position

העברת position ל-!tt עוברת לנקודת הזמן הזו.6

!tt 0          ; תחילת ה-trace
!tt 50         ; בערך מיקום 50%
!tt 100        ; סוף ה-trace
!tt 1A0:12F    ; למיקום 1A0:12F

Position הוא זוג מספרים הקסדצימליים, sequencing number:step count.6 לאובייקט Position יש את המאפיינים Percent (החלק מה-trace), Sequence, ו-Steps, פלוס SeekTo(), שעובר ל-position הזה, ו-ToSystemTime(), שמחזיר את זמן שעון הקיר המשוער (UTC).7 בחקירות ארוכות-ריצה, ToSystemTime() הוא מה שעושה את ההבדל, כי הוא מאפשר להתאים חותמות זמן בלוג האפליקציה מול positions ב-trace. גם לתוצאות TTD.Calls יש SystemTimeStart / SystemTimeEnd.12

התאמת positions מול חותמות זמן בלוגהחל מחותמת זמן בלוג האפליקציה, עוקבים אחרי זמן שעון הקיר המשוער של אובייקט Position כדי לזהות את ה-position ב-trace, עוברים לשם עם SeekTo, וקוראים את הביצוע סביבולוג האפליקציה: זמן החריגהמוצאים position ש-ToSystemTime שלו קרובעוברים לשם עם SeekToקוראים את הקריאות והערכים סביב

איור 17: “מה הוא עשה רגע לפני שורת הלוג הזו?” אפשר לחפש דרך ההתאמה בין positions לזמן שעון קיר.

Positions שימושיים גם לשיתוף. כשמוסרים trace לעמית, מצרפים את ה-position של !tt x:y והם יכולים להתחיל להסתכל מאותה נקודת זמן. כתיבת טווחי position בדוחות באגים היא גם פרקטיקה מומלצת רשמית.2

6.3 הרצה אחורה

הוספת - לפקודות הצעידה הרגילות זזה אחורה בזמן.13

פקודה משמעות כפתור Ribbon
p- חוזרים הוראה אחת (או שורת מקור אחת). קריאת פונקציה נספרת כצעד אחד Step Over Back
t- חוזרים הוראה אחת (או שורת מקור אחת). נכנסים לקריאות פונקציה Step Into Back
g- מבצעים לאחור. נעצרים כשפוגעים ב-breakpoint, באירוע, או בתחילת ה-trace Go Back

האירועים שעוצרים g- הם אותם אלה שעוצרים g קדימה.13 כלומר, מגדירים ba (breakpoint מסוג break-on-access) או bp ומריצים g-, והולכים ישר אחורה ל-“ה-position האחרון שבו התנאי הזה החזיק.” זה הצעד הבסיסי של פרק 7.

בחירה בין פקודות לאחורp- חוזר צעד אחד מעבר לקריאות פונקציה, t- חוזר הוראה אחת לתוך פונקציות, ו-g- הולך ישר אחורה ל-breakpoint, לאירוע, או לתחילת ה-trace. כל מה שעוצר g קדימה עוצר גם g-p-t-g-Position נוכחיחוזרים צעד אחד מעבר לקריאותחוזרים הוראה אחת לתוך פונקציותהולכים ישר אחורה לתנאי העצירה הבאba / bp / אירוע / תחילת ה-trace

איור 18: כדי להסתכל בזהירות בקרבת מקום, משתמשים ב-t-; כדי לקפוץ לסיבה רחוקה, משתמשים ב-ba + g-.

6.4 כניסה דרך Events

אם לא בטוחים מאיפה להתחיל לקרוא, מתחילים מרשימת האירועים. @$curprocess.TTD.Events מפרט יצירה וסיום של threads, טעינה ופריקה של מודולים, ו-exceptions כאירועים.14

dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)

אירוע exception מכיל את ה-position, הסוג (Software / Hardware), קוד ה-exception, ואת program counter באותו זמן, ולחיצה על קישור [Time Travel] בפלט עוברת ל-position הזה.15 ה-walkthrough הרשמי עוקב אחרי הזרימה הזו: הוא קופץ ל-position של access violation (0xc0000005), חושד בשיבוש stack כי מצביע ה-stack ומצביע הבסיס אינם מסכימים, וצועד אחורה שלוש הוראות עם t- כדי לבדוק את הערכים.5 חלון Timelines של WinDbg ממחיז exceptions, breakpoints, גישות זיכרון וקריאות פונקציה כציר זמן, ולחיצה כפולה על exception מנפיקה את אותו SeekTo().16

6.5 Threads ו-Positions

!positions מציג כל thread פעיל ב-position הנוכחי יחד עם ה-position של כל thread ב-trace.17 יש כאן מלכוד אחד. החלפת threads עם ~<number>s אינה מזיזה את ה-position ב-trace. ה-position שהדיבאגר משתמש בו לקריאת זיכרון אינו משתנה, לכן כדי להסתכל על הזיכרון של thread אחר “באותה נקודת זמן,” עוברים לשם עם קישור ה-position בפלט !positions או עם !tt x:y.13

הנתיב הבסיסי דרך replayפותחים את ה-trace ובונים את ה-index, עוברים מרשימת האירועים ל-position של ה-exception, צועדים אחורה לסיבה, ואם צריך עוברים ל-position של thread אחר עם positionsפותחים את ה-trace ומאנדקסיםמוצאים את ה-exception ב-TTD.Eventsעוברים ל-position עם [Time Travel]חוזרים עם t- / p- / g-בודקים positions של threads אחרים עם !positions~s אינו מזיז את position ה-trace

איור 19: “נכנסים דרך אירוע, הולכים אחורה” הוא הנתיב הבסיסי דרך replay. החלפת threads אינה הזזת positions.

7. מציאת “מתי” עם שאילתות — TTD.Calls ו-TTD.Memory

החוזק האמיתי של replay הוא שאפשר לשאול את כל ה-trace. אובייקטי TTD חשופים דרך debugger data model (פקודת dx), ואפשר לסנן, למיין ולאגד אותם בסגנון LINQ.18

7.1 TTD.Calls — חיפוש קריאות פונקציה

@$cursession.TTD.Calls("module!symbol") אוסף את הקריאות לפונקציה שצוינה מכל ה-trace. Wildcards מותרים, וכל קריאה נושאת את positions ההתחלה והסיום שלה (TimeStart / TimeEnd), מזהה ה-thread (עם UniqueThreadId שלעולם אינו בשימוש חוזר), הארגומנטים (Parameters[]), ערך ההחזרה (ReturnValue), וכתובת החזרה (ReturnAddress).12

הדוגמה בתיעוד הרשמי היא GetLastError. מאגדים את הקריאות שערך ההחזרה שלהן אינו אפס לפי קוד שגיאה, ומקבלים רשימה של אילו שגיאות קרו כמה פעמים במהלך ה-trace.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

בניית שאילתת TTD.Callsאוספים קריאות לפי שם פונקציה, מסננים לפי ערך החזרה או ארגומנטים, מאגדים לפי קוד שגיאה וכדומה, ממיינים לפי זמן, ועוברים ל-position של הקריאה שרוצים דרך קישור Time TravelTTD.Calls (שם פונקציה, wildcards)Where: סינון לפי ערך החזרה או ארגומנטיםGroupBy: איגוד לפי קוד שגיאה וכו'OrderBy: מיון לפי זמןעוברים דרך [Time Travel] ב-TimeStart

איור 20: הדרך להשתמש בשאילתות היא להתחיל לא מ-“איפה” אלא מ-“מתי, כמה פעמים, ועם אילו ארגומנטים.”

מילה על סימולים. TTD קובע את מספר וסוגי הארגומנטים של פונקציה, את סוג ההחזרה שלה, ואת מוסכמת הקריאה ממידע סימול PDB. עם private symbols מקבלים את שם הפונקציה ואת הארגומנטים הנכונים. עם public symbols בלבד, מקבלים את שם הפונקציה וארגומנטים ברירת מחדל (ארבעה integers של 64-bit). מודול בלי סימולים בכלל מקבל את שם הפונקציה UnknownOrMissingSymbols.1218 לסוגי PDB ואיך לאחסן אותם, ראו “מה זה PDB?”.

זמינות סימולים ותוצאות TTD.Callsעם private symbols מקבלים את שם הפונקציה ואת הארגומנטים הנכונים; עם public symbols בלבד מקבלים את שם הפונקציה וארבעת ארגומנטי integer של 64-bit ברירת מחדל; בלי סימולים שם הפונקציה הופך ל-UnknownOrMissingSymbolsprivate symbolspublic symbolsאיןסימולים למודול?שם פונקציה + ארגומנטים וערך החזרה נכוניםשם פונקציה + ארגומנטים ברירת מחדל (4 x 64-bit integers)UnknownOrMissingSymbols

איור 21: האם שמרתם את ה-PDBs של המודולים שלכם קובע ישירות כמה השאילתות שימושיות.

Calls כולל חישוב, כך שככל שה-trace גדול יותר, זה לוקח יותר זמן ושימוש CPU גבוה יותר. תוצאות נשמרות ב-cache בזיכרון, כך שהשאילתות השנייה ואילך לאותה פונקציה מהירות יותר.12 כששאילתה מחזירה כלום, יש ארבע סיבות: איך הקריאה כתובה (בודקים את שם המודול עם פקודת x; אם זה חוזר באותיות גדולות, משתמשים בזה), ה-DLL היעד עדיין לא טעון ב-position הזה (עוברים ל-position אחרי הטעינה ומריצים שוב), הפונקציה היא inlined (לא ניתן לעקוב), או שה-wildcard רחב מדי (מצמצמים).18

7.2 TTD.Memory — חיפוש גישות זיכרון

@$cursession.TTD.Memory(start address, end address, "access type") אוסף גישות לטווח הזיכרון שצוין מכל ה-trace. הסוגים הם r (קריאה), w (כתיבה), rw, e (הרצה), rwe, ו-ec (הרצה/שינוי).19 “מי כתב אחרון למשתנה הזה?” נענה באיסוף עם "w", לקיחת .Last(), ומעבר ל-position הזה.5

dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()

אם צריך לחפש רק קדימה או אחורה מה-position הנוכחי, @$curprocess.TTD.PrevMemoryAccess("w", address, size) / NextMemoryAccess קלים יותר ומקבלים כמה טווחים בבת אחת. את ה-position שבו register השתנה אפשר למצוא עם @$curthread.TTD.PrevRegisterWrite("rcx").6

7.3 ba + g- — לגרום לדיבאגר לענות “מי שיבש?”

דחיסת הנוהל שמוצג ב-walkthrough הרשמי לצורה שמישה לחקירות ארוכות-ריצה נותנת את הבא.5

  1. עוברים ל-position של ה-exception עם TTD.Events
  2. צועדים אחורה עם t- ומניחים איזה משתנה מחזיק את הערך המשובש
  3. מקבלים את כתובת המשתנה הזה עם dx &variable
  4. מגדירים breakpoint כתיבה עם ba w4 <address>
  5. מריצים g- כדי לחזור ישר ל-position שבו המשתנה הזה נכתב אחרון
  6. בודקים אם הנקודה הזו (או כמה הוראות קודם) היא הסיבה. אם הערך שנכתב הגיע ממשתנה אחר, מגדירים ba על המשתנה הזה ומריצים g- שוב
  7. חוזרים עד שמגיעים להוראה ששיבשה
מעקב אחרי מקור ערך עם ba ו-g-מגדירים breakpoint כתיבה על כתובת הערך המשובש ומבצעים לאחור, נעצרים בהוראה שכתבה אחרונה, ואם הערך הגיע ממשתנה אחר חוזרים על אותם שלבים עד שמגיעים להוראה ששיבשהכןלאמזהים את כתובת הערך המשובשמגדירים breakpoint כתיבה עם ba wמבצעים לאחור עם g-נעצרים בהוראה שכתבה אחרונההערך הגיע ממשתנה אחר?ההוראה המשבשת = הסיבה

איור 22: חקירה שמסתיימת ב-“זה משובש” עם dump מתקדמת מכנית ל-“מי שיבש” עם TTD.

TTD 1.11.553 ואילך גם מוסיפים @$curframe.TTD.VariableHistory(), שמחזיר את ההיסטוריה של ערכי משתנים מקומיים של frame. הוא מציג טבלה של שמות המשתנים ואילו ערכים כל משתנה החזיק באילו טווחי position.11 במצבים כמו שיבוש stack שבהם רוצים לדעת “מאז מתי הערך שגוי,” זה שימושי לצמצום איפה להגדיר ba קודם.

8. יישום לבאגים ארוכי-ריצה

עכשיו מיישמים את הכלים האלה על שלושת סוגי המקרה שרשומים בהתחלה.

סוגי באגים ארוכי-ריצה ונקודת הכניסה של TTD לכל אחדל-exceptions לסירוגין ולשיבוש נתונים, חוזרים מהאירוע עם ba ו-g-; לגידול משאבים, מתאימים קריאות רכישה ושחרור עם TTD.Calls; ל-not responding ולשרשרות המתנה, עוקבים אחרי positions וזמני שעון הקיר של קריאות wait APIExceptions לסירוגין, שיבוש נתוניםTTD.Events, ואז ba + g-גידול handle וזיכרוןמתאימים רכישה ושחרור עם TTD.CallsNot responding, שרשרות המתנה!positions וזמני שעון קיר של wait APIsמקליטים עם -module מוגבל ל-DLL שלכם

איור 23: נקודת הכניסה נבדלת לפי סוג. מה שהם חולקים הוא צמצום היקף ההקלטה לפני שמתחילים לקרוא.

סוג 1: Exceptions לסירוגין ושיבוש נתונים. שומרים את הרגעים לפני התסמין עם ring buffer של סעיף 5.1, קופצים ל-position של ה-exception מרשימת האירועים של סעיף 6.4, ועוקבים אחרי מקור הערך עם ba + g- של סעיף 7.3. ההבדל מחקירת dump הוא שהחקירה אינה מסתיימת כשמוצאים “את המשתנה המשובש”; משם ממשיכים מכנית אחורה.

סוג 2: גידול handle וזיכרון. זה סוג המקרה שמפורק ב-“Handle leak באפליקציית מצלמה תעשייתית שקורסת אחרי ריצה ארוכה.” אי אפשר להקליט חודש שלם, לכן מגבילים את ההקלטה ל-DLL שלכם עם -module של סעיף 5.2, משלבים עם -ring, ושומרים “את הדקות הבודדות בזמן שזה גדל.” ב-trace, אוספים TTD.Calls("kernelbase!CreateFileW") ו-TTD.Calls("kernelbase!CloseHandle") ומתאימים את ערך ההחזרה של CreateFileW (ערך ה-handle) מול הארגומנט הראשון של CloseHandle (Parameters[0]). ה-ReturnValue של CloseHandle הוא דגל הצלחה בוליאני, כך שהוא חסר תועלת להתאמה. אם ערך handle נשאר לא סגור, ה-ReturnAddress (הקורא) של קריאת CreateFileW הזו נותן לכם את הקורא שדולף.12 עם זאת, תצפית אם דליפה קיימת וכמה גדולה היא זולה יותר עם Application Verifier או ספירת handles; חלוקת העבודה הנכונה היא להביא TTD בשלב של קיבוע איזה נתיב דולף. שימו לב שהקלטה עם Application Verifier מופעל פוגעת בבירור בביצועי replay בגלל איך הזיכרון בשימוש, לכן מכבים אותו בזמן הקלטה.8

סוג 3: “Not responding” ושרשרות המתנה. כפי שכתוב ב-“מה Not Responding באמת אומר,” hang הוא שאלה של מי ממתין למי. Trace אינו גדל בזמן idle,4 כך שזמן ההמתנה של thread שממתין ב-kernel אינו מופיע כהוראות. מה שמופיע הוא הקריאה רגע לפני הכניסה להמתנה וההוראות אחרי שהיא חזרה. מסתכלים על ה-position של כל thread עם !positions,17 ומיישרים “איזה thread התחיל להמתין למה, ומתי” מ-SystemTimeStart / SystemTimeEnd של TTD.Calls("kernelbase!WaitForSingleObject") וכדומה; בהתאמה מול חותמות זמן בלוג, זה מאפשר לשחזר את שרשרת ההמתנות.12 “באגים תלויי סדר” כמו DllMain ו-loader lock או spurious wakeups של condition variables הם אזור שבו TTD, שמתעד את הסדר עצמו, מתאים.

9. TTD עם אפליקציות .NET

התיעוד הרשמי של TTD מציין שקוד מנוהל ניתן לדיבוג עם TTD ב-WinDbg באמצעות הרחבת SOS (sos.dll) שרצה במצב 64-bit.1 הטעינה זהה לפרק 3 של מאמר אנליזת SOS (.loadby sos coreclr או טעינה אוטומטית), ו-!clrstack ו-!pe עובדים בכל position ב-trace. הנתיב הבסיסי הוא לעבור ל-position של ה-exception עם TTD.Events ואז לקרוא את ה-stack המנוהל עם !clrstack.

הכנה לקריאת TTD trace של אפליקציית .NETפותחים את TTD trace ב-WinDbg, טוענים את הרחבת SOS של 64-bit, עוברים ל-position של אירוע ה-exception, וקוראים את המצב המנוהל עם !clrstack ו-!pe. משתמשים ב-TTD.Calls לקריאות מעבר לגבולות nativeTTD trace (.run)WinDbgהרחבת SOS (64-bit)עוברים ל-position של ה-exception עם TTD.Eventsקוראים עם !clrstack / !peTTD.Calls לקריאות מעבר לגבולות native

איור 24: מצב מנוהל דרך SOS, חיפושי קריאה בגבולות native. מפרידים את התפקידים ולא הולכים לאיבוד.

שני סייגים. ראשית, מה ש-Microsoft מבטיחה הוא “SOS במצב 64-bit.” שום דבר אינו נאמר על אפליקציות .NET שנבנו ל-x86, לכן מריצים את יעד החקירה כ-x64 אם אפשר. שנית, TTD.Calls מסתמך על מידע סימול PDB.12 לא סומכים על חיפוש מתודות מנוהלות שעברו JIT לפי שם; השימוש האמין הוא לעקוב אחרי קריאות מעבר לגבולות native כמו יעדי P/Invoke ב-DLLs native, COM ו-Win32 APIs. בעיות heap מנוהל כמו אלה ב-“להבדיל ב-.NET בין המתנה ל-GC לבין memory leak” נרדפות קודם עם dotnet-counters / dotnet-gcdump / !gcroot על dump, ו-TTD יוצא בשלב שבו מעורב גבול native.

10. בחירה בין dumps, לוגים, ETW ו-TTD

TTD אינו תרופת פלא, והוא אינו מחליף את הכלים הקיימים. כאן הוא מיושר מול הכלים שמכוסים במאמרים שלנו.

מה רוצים לדעת כלי להשתמש בו קודם מתי TTD נכנס
המצב ברגע הקריסה Crash dump (WER LocalDumps / ProcDump) כשה-dump מראה “זה משובש” אבל לא מי שיבש
איזה קובץ או מפתח Registry נכשל Process Monitor כשצריך גם איך נבנו הארגומנטים שהועברו ל-API שנכשל
ביצועים של כל ה-PC לאורך זמן WPR/WPA, PerfView (ETW) כשזו בעיית נכונות ולא ביצועים וצריך סדר ברמת הוראה
רצף האירועים ברמה העסקית הלוגים של האפליקציה כשזה קרה בנתיב קוד בלי לוגים (TTD מתעד הכול בלי שינויי קוד מראש1)
מי כתב את הערך הזה, סדר הקריאות TTD
בחירת שיטת חקירהקודם תופסים את המצב עם dumps ולוגים קלים, חוקרים APIs שנכשלים עם ProcMon וביצועים עם ETW, ועוברים ל-TTD רק כשעדיין צריך מי העביר מה ומתיכןלאתסמיןתופסים את המצב עם dumps ולוגים (קל)APIs שנכשלים: ProcMonביצועים: WPR/WPA, PerfViewצריך מי, מתי, ומה הועבר?מתכננים את היקף ההקלטה ומקליטים עם TTDמיושב עם כלים קלים

איור 25: תופסים את “התוצאה” עם כלים קלים קודם, ומוציאים TTD רק למקרים שבהם מתברר שה”נתיב” נחוץ.

הסדר הוא “הכי קל קודם.” Dumps ולוגים עולים כמעט כלום לאיסוף, לכן משאירים אותם במקום באופן קבוע; מוציאים TTD, אחרי התכנון של פרק 5, למקרים שבהם קריאת ה-dump הראתה שהנתיב נחוץ. בהתחשב ב-overhead של TTD ובכך ש-traces מכילים מידע סודי, אין סיבה להפוך את הסדר הזה.

11. הערות תפעול

  • מתייחסים ל-traces כקבצים סודיים. הקלטה מכילה תוכן זיכרון ויכולה לכלול מידע אישי וקשור לאבטחה כמו נתיבי קבצים, נתוני Registry ותוכן של זיכרון וקבצים.12 כשמקליטים בסביבת לקוח, מבינים מראש מה עשוי להיכלל (connection strings, tokens, נתוני לקוחות) ומחליטים על ערוץ העברה מוצפן, מיקום אחסון ותקופת שמירה.
  • משתפים רק את קובץ .run. קובץ .idx גדול בערך כמו קובץ .run ונוצר אוטומטית כש-WinDbg פותח אותו. קבצי .run נדחסים היטב. כשמדווחים באג ב-TTD עצמו, מצרפים גם את קובץ .out.2
  • מיישרים גרסאות. TTD ממשיך להתעדכן יחד עם WinDbg; 1.11.611 כולל תיקון לקריסות הקלטה בתוכניות שמשתמשות ב-AVX/AVX512 ושינוי בפורמט ה-index.11 ערבוב גרסה ישנה בצד ההקלטה או בצד ה-replay פירושו אינדוקס מחדש או הקלטה מחדש.
  • בודקים -replayCpuSupport כשמריצים replay על CPU שונה. ברירת המחדל מעדיפה ניידות, ו-MostConservative מסופק למקרים שבהם CPU ההקלטה ו-CPU ה-replay שונים (כמו replay של trace Intel על arm64). ולהפך, אם יודעים ש-CPU ה-replay שווה או טוב יותר, אפשר לבחור הקלטה קטנה ומהירה יותר.2
  • אפשר להקליט גם Windows Server. TTD.exe תומך ב-Windows Server 2016/2019/2022/2025.2
  • בידוד כשל להקליט. קודם מנסים אם אפשר להקליט ping.exe או cmd.exe; אם לא, חושדים בהתנגשות עם תוכנה חודרנית כמו אנטי-וירוס או וירטואליזציית אפליקציות.8
נוהל טיפול ב-tracesכי trace מוקלט מכיל תוכן זיכרון, מבינים איזה מידע הוא עשוי להכיל, דוחסים רק את קובץ .run ומוסרים דרך ערוץ מוצפן, מחליטים על מיקום האחסון ותקופת השמירה, ובצד האנליזה פותחים עם אותה גרסת WinDbg ובונים את ה-indexהקלטה הושלמה (.run / .idx / .out)מבינים איזה מידע הוא עשוי להכילדוחסים רק .run, מצפינים ומוסריםמחליטים על מיקום האחסון ותקופת השמירהפותחים עם אותה גרסת WinDbg ויוצרים .idx

איור 26: מסירת trace היא מסירת קובץ סודי. מחליטים על הנוהל לפני שמקליטים.

12. סיכום

  • Dump הוא “מצב”; TTD הוא “הנתיב.” במקרים שצריכים את מקור ערך משובש או את סדר הקריאות, TTD הופך את החקירה לעבודה מכנית
  • ההקלטה איטית פי 5 עד 20, גדלה 5 עד 50 MB לשנייה, ואינה יכולה לנתק. לאפליקציות ארוכות-ריצה, מתכננים קודם את היקף ההקלטה עם -ring/-maxFile, -module, -recordmode Manual, ו--monitor
  • Replay הוא “נכנסים דרך אירוע, הולכים אחורה”: TTD.Events, ואז [Time Travel], ואז t-/g-, ו-ba + g- כדי לגרום לדיבאגר לענות “מי שיבש”
  • TTD.Calls ו-TTD.Memory הן שאילתות על כל ה-trace. מתאימים מול חותמות זמן בלוג עם ToSystemTime() ו-SystemTimeStart
  • ל-.NET, קוראים את המצב עם SOS של 64-bit ומשתמשים ב-TTD.Calls בגבולות native
  • Trace הוא קובץ סודי. משתפים רק את קובץ .run, ומחליטים על הערוץ והאחסון קודם

מקרים שבהם dump נלכד אבל הסיבה מחוץ להישג, או שבהם העבודה נעצרה כי הבאג אינו משתחרר, מיושבים לא פעם עם TTD ברגע שאפשר לתכנן את היקף ההקלטה. אפשר גם לקחת על עצמנו הכול מתכנון ההקלטה עד אנליזת קובץ .run, אז אפשר לפנות עם ה-dumps והלוגים.

מאמרים קשורים

תחומי ייעוץ קשורים

KomuraSoft LLC מטפלת בחקירת שורש של באגי אפליקציות Windows שמופיעים רק אחרי ריצות ארוכות או לסירוגין, בשילוב crash dumps, לוגים ו-TTD traces; בבניית מערך חקירה כולל תכנון היקף ההקלטה; ובבידוד כשלים שמערבים גבולות native (COM, P/Invoke, SDKs של התקנים). פונים כבר בשלב של “יש dump אבל לא מגיעים לסיבה.”

קישורים

  1. Microsoft Learn, Time Travel Debugging - Overview. על כך ש-TTD מקליט את ביצוע התהליך ומריץ אותו קדימה ואחורה, ש-dumps נוטים להחמיץ את המצב ואת נתיב הביצוע שהובילו לכשל, שהקלטה דורשת הרשאות Administrator, שהקלטות עשויות להכיל מידע אישי וקשור לאבטחה, טבלת השוואת שיטות חקירה, התפקידים של .run/.idx, ודיבוג קוד מנוהל עם הרחבת SOS במצב 64-bit.  2 3 4 5 6 7 8 9 10

  2. Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. על ההאטה פי 5 עד 20 או יותר, אי-יכולת לנתק את עצמו אחרי attach, תמיכה ב-Windows Server 2016 עד 2025, התקנה ופריסה offline, שלושת המצבים -launch/-attach/-monitor, האפשרויות -out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode, קריאת קובץ .out, ועצות לשיתוף traces.  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

  3. Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. על אי-תאימות עם אנטי-וירוס ותוכנות ניטור זיכרון ועם Electron, user mode בלבד, replay לקריאה בלבד, אי-יכולת להזריק ל-protected processes (PPL), והשפעת ביצועים של בערך פי 10 עד 20 בזמן הקלטה.  2 3 4 5 6 7

  4. Microsoft Learn, Time Travel Debugging - Working with Trace Files. על גורמי גודל ה-trace (bit אחד עד בית אחד להוראה), גידול של 5 עד 50 MB לשנייה כשפעיל ואפס כש-idle, היעדר תקרת גודל מקסימלית, ה-index שהוא פי 1 עד 2 מה-trace, והתנהגות ההקלטה והאינדוקס כשהדיסק נגמר יחד עם ה-workaround.  2 3 4 5 6 7 8 9

  5. Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. על הנוהל הכללי של מעבר ל-position של אירוע exception וחזרה עם ba ו-g- ל-position שבו נכתב ערך לא תקין אחרון, נקודת הכשל שלעתים קרובות נמצאת בתוך טיפול בשגיאות כמה צעדים אחרי הסיבה האמיתית, ה-trace שנסגר בקריסה ו-WinDbg שמאנדקס אותו אוטומטית, והשימוש ב-TTD.Memory וב-.Last() 2 3 4 5 6 7

  6. Microsoft Learn, !tt (time travel). על ציון positions ל-!tt (אחוז או xx:yy), משמעות שני רכיבי position (מספר רצף וספירת צעדים), ו-TTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess 2 3 4

  7. Microsoft Learn, TTD Position Objects. על Percent/Sequence/Steps של אובייקט Position, SeekTo(), ToSystemTime() שמחזיר את זמן שעון הקיר המשוער (UTC), ו-FFFFFFFFFFFFFFFE:0 שמציין את סוף ה-trace.  2

  8. Microsoft Learn, Time Travel Debugging - Troubleshooting. על כך שנדרש elevation, שהקלטת הפעלה של אפליקציות UWP אינה נתמכת, ש”תהליכים חריגים” ב-session או הקשר אבטחה אחר מחוץ להיקף, בידוד עם ping.exe/cmd.exe, האטת replay כשמשתמשים ב-Application Verifier באותו זמן, ובניית index מחדש עם !index -status/!index -force 2 3 4 5

  9. Microsoft Learn, Time Travel Debugging - Record a trace. על הקלטה מ-Launch executable (advanced) / Attach to process ב-UI של WinDbg, תיבת הסימון Record with Time Travel Debugging, הגדרת מיקום השמירה עם Configure and Record, והגבלת מודולים עם Record subset of execution

  10. Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). על תיעוד API ההקלטה הפנימי ש, בשילוב עם -recordmode Manual, מאפשר לתוכנית לשלוט בהתחלה ובעצירה של ההקלטה. 

  11. Microsoft Learn, Time travel debugging release notes. על תיקון ההקלטה לתוכניות שמשתמשות ב-AVX/AVX512 ושינוי פורמט ה-index (נדרש אינדוקס מחדש) ב-1.11.611, ו-@$curframe.TTD.VariableHistory() שנוסף ב-1.11.553.  2 3

  12. Microsoft Learn, TTD Calls Objects. על הארגומנטים של TTD.Calls, המאפיינים ThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd, ברירות המחדל כשחסר מידע סימול PDB (ארבעה ארגומנטי integer של 64-bit, UnknownOrMissingSymbols), והחישוב שלוקח זמן עם תוצאות שנשמרות ב-cache.  2 3 4 5 6 7

  13. Microsoft Learn, Time Travel Debugging - Replay a trace. על ביצוע לאחור עם p-/t-/g-, g- שנעצר באותם אירועים כמו ביצוע קדימה, !positions, ו-~s שאינו משנה את ה-position ב-trace.  2 3

  14. Microsoft Learn, TTD Event Objects. על סוגי האירועים (ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception) ואובייקטי הבן Position, Module, Thread ו-Exception. 

  15. Microsoft Learn, TTD Exception Objects. על Type (Software/Hardware), ProgramCounter, Code, Flags ו-Position של אובייקט exception. 

  16. Microsoft Learn, WinDbg: Timelines. על חלון Timelines שממחיז exceptions, breakpoints, גישות זיכרון וקריאות פונקציה, ולחיצה כפולה על exception שמנפיקה Position.SeekTo()

  17. Microsoft Learn, !positions. על הצגת כל thread פעיל ואת ה-position של כל thread ב-trace.  2

  18. Microsoft Learn, Introduction to Time Travel Debugging objects. על האובייקטים @$curprocess.TTD / @$cursession.TTD, שאילתות עם OrderBy/Where/Select/GroupBy, דוגמאות איגוד שגיאות GetLastError ומציאת הקריאה האחרונה ל-MessageBoxW, משמעות UnknownOrMissingSymbols, וארבע הסיבות ש-Calls מחזיר כלום.  2 3 4 5

  19. Microsoft Learn, TTD Memory Objects. על סוגי הגישה של TTD.Memory (r/w/rw/e/rwe/ec) ומעבר ל-position דרך קישור [Time Travel] בתוצאות. 

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

מה ההבדל בין Time Travel Debugging (TTD) לבין crash dump?
Crash dump הוא תצלום של הזיכרון ברגע הקריסה: הוא מראה את המצב באותו רגע, אבל לא את הנתיב שהוביל לשם. TTD trace הוא הקלטה מלאה של ביצוע ההוראות של ה-process שאפשר אחר כך להריץ קדימה ואחורה, כך שאפשר לחזור אחורה ולבדוק ישירות מי כתב אחרון למשתנה או מה נקרא רגע לפני exception. גם התיעוד הרשמי של Microsoft מציין ש-dumps נוטים להחמיץ את המצב ואת נתיב הביצוע שהובילו לכשל. המחיר הוא שהתהליך רץ פי 5 עד 20 לאט יותר בזמן ההקלטה, וקובץ ה-trace גדל בערך ב-5 עד 50 MB לשנייה כשהתהליך פעיל.
אפשר להשאיר TTD מחובר לאפליקציית ייצור שרצה ימים?
לא כפי שהוא. TTD מאט את התהליך משמעותית בזמן הקלטה, ה-trace גדל ב-5 עד 50 MB לשנייה בתהליך פעיל, ואין תקרה לגודל הקובץ. שימוש באפליקציה ארוכת-ריצה מניח תכנון הקלטה: -ring (ring buffer) של TTD.exe עם -maxFile כדי לשמור רק את N ה-MB האחרונים, -module כדי להקליט רק בזמן שמודול שלכם רץ, או -recordmode Manual כדי שהאפליקציה תבחר את מרווח ההקלטה. וגם, אחרי attach, TTD אינו יכול לנתק את עצמו, לכן צריך להחליט מראש איך מסיימים את ההקלטה, כלומר לסיים (restart) את התהליך.
אפשר להקליט services ותהליכים ב-sessions אחרים?
התיעוד של TTD.exe מתאר -attach כמיועד לחקירת services ואפליקציות ארוכות-ריצה, ו-monitor כמקליט בכל פעם שתוכנית או service עולה. מצד שני, דף ה-troubleshooting מציין שתהליכים חריגים שרצים ב-session אחר או בהקשר אבטחה שונה אינם נתמכים כרגע להקלטה. האם זה באמת עובד תלוי בסביבה, לכן קודם מקליטים תהליך פשוט כמו ping.exe או cmd.exe באותה תצורה כמו הייצור, אחר כך מנסים את תהליך היעד, ורק אז בונים את זה לתפעול.
אפשר להשתמש ב-TTD על traces של אפליקציות .NET?
כן. התיעוד הרשמי מציין שהרחבת SOS (sos.dll) שרצה במצב 64-bit יכולה לשמש על TTD trace ב-WinDbg לדיבוג קוד מנוהל. התבנית הבסיסית היא להריץ פקודות SOS כמו !clrstack ו-!pe בכל position ב-trace, לעבור ל-position של אירוע exception ואז לקרוא את ה-stack המנוהל. שאילתת TTD.Calls, שמחפשת קריאות לפי שם סימול, מסתמכת על מידע PDB, לכן באפליקציות .NET השימוש האמין הוא לעקוב אחרי קריאות מעבר לגבולות native כמו P/Invoke, COM ו-Win32 APIs.
בטוח לשלוח קובץ trace (.run) לחברה אחרת או מחוץ לארגון?
לא כפי שהוא. הקלטת TTD מכילה את תוכן הזיכרון של התהליך, והתיעוד הרשמי מציין במפורש שהיא יכולה לכלול מידע אישי או סודי כמו נתיבי קבצים, נתוני Registry ותוכן של זיכרון וקבצים. אם שולחים, קודם מבינים מה הוקלט (connection strings, tokens, נתוני לקוחות וכן הלאה), ואז מחליטים על ערוץ העברה מוצפן ועל מיקום אחסון. שיתוף רק של קובץ .run מספיק; קובץ ה-index (.idx) נוצר אוטומטית כש-WinDbg פותח אותו.
TTD.Calls מחזיר כלום כשאני מחפש פונקציה. למה?
יש ארבע סיבות עיקריות. ראשית, סימולים: פונקציות במודול בלי PDB נקראות UnknownOrMissingSymbols, ושם המודול עשוי להיות באותיות גדולות, לכן בודקים את שם הסימול בפועל עם פקודת x. שנית, ה-DLL היעד עשוי עדיין לא להיות טעון ב-position הזה; עוברים ל-position אחרי שה-DLL נטען ושואלים שוב. שלישית, אם הפונקציה היא inlined, מנוע השאילתה אינו יכול לעקוב אחריה. רביעית, ה-wildcard עשוי להיות רחב מדי ולהתאים ליותר מדי פונקציות; מצמצמים את התבנית.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג