חקירת חיים מקצה לקצה¶
נניח שהתוכנית פועלת יפה במשך דקה, אבל אחרי אלף בקשות מתחילה להיכשל ביצירת משאבים. חיפוש לפי שורת הקריסה בלבד עלול להטעות: הכשל האחרון הוא תוצאה של משאבים שנשארו חיים לאורך כל הדקה.
חקירת חיים מחברת זהות, זמן ובעלות. צריך לדעת איזה process ביצע פעולה, אילו threads השתתפו, איזה אובייקט נוצר ומי היה אמור לשחרר אותו. הכלים נותנים זוויות שונות, ולכן אין לצפות שכל כלי יראה כל סוג אירוע.
מייצרים דליפה מוגבלת שאפשר לזהות¶
התוכנית הבאה כוללת מצב פגום במכוון. במצב רגיל היא סוגרת כל event מיד; במצב --leak היא מאבדת את ההפניה אליו עד סיום התהליך. הניסוי מוגבל לחמש מאות אובייקטים בתהליך שלו.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <cwchar>
int main(int argc, char** argv) {
const bool leak = argc == 2 && std::strcmp(argv[1], "--leak") == 0;
std::printf("pid=%lu mode=%s\n",
GetCurrentProcessId(), leak ? "leak" : "closed");
DWORD before = 0;
if (!GetProcessHandleCount(GetCurrentProcess(), &before)) return 1;
unsigned created = 0;
for (unsigned i = 0; i < 500; ++i) {
wchar_t name[128]{};
swprintf_s(name, L"Local\\AmitTech.HandleLab.%lu.%u",
GetCurrentProcessId(), i);
HANDLE event = CreateEventW(nullptr, TRUE, FALSE, name);
if (!event) {
std::printf("create error=%lu\n", GetLastError());
break;
}
++created;
if (!leak) CloseHandle(event);
}
DWORD after = 0;
if (!GetProcessHandleCount(GetCurrentProcess(), &after)) return 2;
std::printf("created=%u before=%lu after=%lu\n", created, before, after);
Sleep(10000);
}
ההשהיה נועדה לתת זמן לפתוח כלי חקירה, ואינה חלק מתיקון הדליפה. בסיום התהליך מערכת ההפעלה משחררת את ה־handles שנותרו, אך אין בכך תיקון לשירות שאמור להישאר חי חודשים.
המספר הכולל יכול להיות מושפע גם מפעולות runtime וכלים. לכן מחפשים את סוג האובייקט ואת השם הייחודי של הניסוי, ולא מסתפקים בהפרש כללי.
מתחילים מזווית רחבה¶
ב־Process Explorer מאתרים את ה־PID ואת נתיב ה־image של הניסוי. מציגים handles בחלונית המתאימה ומסננים לפי השם AmitTech.HandleLab. במצב הפגום צפויים להישאר events פתוחים; במצב התקין הם נסגרים במהלך הלולאה.
הבדיקה צריכה להשתמש בזהות התהליך מההרצה הנוכחית. המזהה PID יכול להיעשות שימוש חוזר, והפעלת עותק חדש אינה המשך של אותו אובייקט process. בעת תחקיר אמיתי שומרים גם זמן יצירה ונתיב, ולא רק מספר.
עץ processes מסייע לראות קשרי יצירה, אבל צילום מאוחר אינו מציג בהכרח הורה שכבר יצא. לשם כך צריך אירועי creation שנאספו בזמן המתאים.
בוחרים כלי שמתאים לשאלה¶
ה־Process Monitor מציג פעילות קבצים, registry ותהליכים ו־threads. הוא מתאים לחקירת נתיב DLL שנבדק, קובץ תצורה חסר או ילד שנוצר עם שורת פקודה שגויה. הוא אינו יומן כללי של כל שינוי ב־Object Manager, ולכן אין לצפות שכל CreateEvent יופיע כאירוע מתאים.
בעת שימוש בו מסננים לפי התהליך והפעולות הרלוונטיות ושומרים את הלוג בפורמט שמאפשר חקירה חוזרת. מסנן תצוגה אינו בהכרח אומר שהמידע לא נאסף; צריך להבין את מצב האיסוף והסינון של הכלי.
אירוע NAME NOT FOUND אינו בהכרח שורש התקלה. תוכנות יכולות לנסות כמה נתיבים כחלק מ־fallback תקין. מחפשים את הרצף ואת התוצאה הסופית, לא את השורה האדומה הראשונה.
יורדים אל התהליך עם סמלים¶
ב־WinDbg אפשר לחבר לתהליך המעבדה ולבדוק handles. הפקודה הבאה מציגה מידע על אובייקטים מסוג Event לפי יכולות ההרחבה וההרשאות הזמינות.
הפלט עוזר לקשר את שם האובייקט לסוג ול־handle. הוא אינו מספק אוטומטית את stack ההקצאה ההיסטורי של כל handle. אם לא אספנו מידע כזה, צריך להפעיל מנגנון מעקב מתאים בהרצה נוספת או להציב breakpoint בפעולת הרכישה שלנו.
אפשר לעצור ב־CreateEventW ולספור מסלולים, אך breakpoint תכוף משנה תזמון. בתחקיר race יש להעדיף לעיתים trace בעל השפעה קטנה יותר, ולציין את השפעת כלי המדידה.
זמן המתנה אינו זמן חישוב¶
ב־ETW וב־WPA בוחנים מתי thread רץ, מתי נעשה ready ומתי עבר להמתנה. אם ה־CPU נמוך אבל הבקשות איטיות, ייתכן שהמערכת מחכה למשאב. אם ready delay גבוה, ייתכן שיש תחרות על מעבד. אם זמן user גבוה, מתחילים לחפש מסלול חישוב.
המספר הכולל של context switches אינו ציון איכות. מעבר יכול להיות נחוץ למערכת מגיבה, והפחתתו יכולה לבוא על חשבון latency או חלוקת עבודה. צריך להסביר את הקשר למטרה שנמדדת.
מתקנים בעלות ומוכיחים מחזור¶
התיקון הישיר בדוגמה הוא סגירת כל event בסוף השימוש. במערכת רחבה יותר מכניסים את ה־handle לבעלים מסוג RAII מיד אחרי הצלחה, כך שכל יציאה מוקדמת עוברת דרך אותו ניקוי.
בדיקת regression מפעילה מחזורי עבודה רבים ובודקת שהמספר מתייצב אחרי warmup. מטמון מכוון יכול לגדול ואז להיעצר; דליפה ממשיכה לגדול ביחס לכמות עבודה ללא גבול שהוגדר.
הדוח צריך להציג שרשרת סיבתית: פעולה מסוימת רכשה handle, מסלול יציאה מסוים איבד אותו, השם והספירה תמכו בכך, והתיקון החזיר יציבות באותו עומס. צילום בודד של מספר קטן אינו הוכחה מספקת.
לקריאה נוספת: היכולות של Process Monitor והכלי Process Explorer.