תשתית לבדיקות failure path ב-Windows עם Application Verifier

· עודכן בתאריך: · · פיתוח Windows, חקירת באגים, מצלמה תעשייתית, Application Verifier, בדיקות failure path, handle leak

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 11 Mar 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173363)

מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.

Go Komura (2026). תשתית לבדיקות failure path ב-Windows עם Application Verifier. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173363 https://comcomponent.com/he/blog/application-verifier-abnormal-test-foundation-part2/

DOI (הגרסה האחרונה)
10.5281/zenodo.22173363
DOI (הגרסה הזו)
10.5281/zenodo.22173364

Application Verifier הוא כלי חזק כשרוצים לחשוף מוקדם תקלות שצצות בקוד native של Windows או בגבול ה-Win32. במיוחד כשרוצים לבדוק בעיות handle, heap corruption, ו-failure path במצב של מחסור במשאבים, אפשר להוציא החוצה מהר בעיות שבבדיקות happy path פשוט לא רואים.

בחלק הראשון, חקירת קריסה בהרצה ארוכה של מצלמה תעשייתית — handle leak, תיארנו מקרה של אפליקציית בקרה שקרסה אחרי הרצה ארוכה, והגורם היה handle leak. אבל לחזק לוגים זה רק חצי מהעבודה. מה שבאמת צריך זה לבדוק מראש שאם בעתיד תהיה טעות תכנות לא צפויה — memory leak, handle leak, כשל באמצע, או שחרור משאב שפספסנו — עדיין נהיה במצב שבו אפשר להבין מה קרה.

לשם כך השתמשנו ב-Application Verifier. זה כלי שמאפשר להוסיף בדיקות ו-fault injection בזמן ריצה לקוד שרץ ב-native של Windows או בגבול ה-Win32. מה שנוח במיוחד בפועל: אפשר לגרום מוקדם להתנהגות שמדמה מחסור בזיכרון או במשאבים, בלי באמת לרוקן את הזיכרון של המכונה.

בחלק השני נסביר מה זה Application Verifier, מה אפשר לעשות איתו, ואיך משלבים אותו בתשתית לבדיקות failure path, בהקשר של אפליקציית בקרת מצלמה תעשייתית.

תוכן עניינים

  1. המסקנה בקצרה
  2. מה זה Application Verifier
    • 2.1. בקצרה
    • 2.2. מתי זה באמת עוזר
    • 2.3. למה זה שימושי בפועל
    • 2.4. התקנה והפעלה מקומית
  3. מה אפשר לעשות עם Application Verifier
    • 3.1. Basics: Handles / Heaps / Locks / Memory / TLS ועוד
    • 3.2. Low Resource Simulation: לחשוף מחסור בזיכרון ובמשאבים בלי לחכות
    • 3.3. Page Heap ו-debugger
    • 3.4. !avrf / !htrace / לוגים
  4. למה הכנסנו את זה הפעם
    • 4.1. המטרה היא לא רק למצוא באג
    • 4.2. לגרום למצב שמדמה מחסור בזיכרון
    • 4.3. לבדוק שאפשר לעקוב אחרי handle misuse כשזה קורה
  5. איך גורמים למצב שמדמה מחסור בזיכרון או במשאבים
    • 5.1. איך Low Resource Simulation עובד
    • 5.2. אילו API אפשר להכשיל
    • 5.3. איך מיישמים את זה בפועל
  6. איך בודקים בעיות handle
    • 6.1. בדיקת Handles
    • 6.2. stack של open / close עם !htrace
    • 6.3. שילוב עם לוגים פנימיים
  7. איך בונים תשתית לבדיקות failure path
    • 7.1. להריץ דרך harness, לא דרך אפליקציית הייצור
    • 7.2. לפצל את תפריט הבדיקות
    • 7.3. מה אוספים
    • 7.4. תנאי pass
    • 7.5. מגבלות ונקודות לשים לב
  8. מתי להשתמש במה
  9. סיכום
  10. מקורות

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

1. המסקנה בקצרה

  • Application Verifier מקל למצוא בזמן ריצה misuse שקורה בגבול ה-unmanaged / native של Windows
  • מה ששימושי זה לא רק “למצוא באג”, אלא לגרום מוקדם ל-failure path שבדרך כלל כמעט לא מופיע
  • ב-Handles מזהים invalid handle, ב-Heaps חושפים heap corruption, וב-Low Resource Simulation עושים fault injection למצב שמדמה מחסור בזיכרון או במשאבים
  • להשאיר חקירת leak של EXE שרץ לאורך זמן רק ל-Application Verifier זו גישה גרועה; בפועל משלבים Handle Count ולוגים פנימיים של resource lifecycle
  • בתשתית לבדיקות failure path, קריא יותר להפריד בין הרצת verifier על happy path לבין הרצה עם fault injection
  • גם כשרוצים לבדוק DLL, היעד שמפעילים עליו את Application Verifier הוא ה-EXE של הבדיקה שמריץ את ה-DLL בפועל

בקיצור, Application Verifier הוא כלי שמוציא החוצה באגים מציקים מסביב ל-native / Win32 של Windows. במיוחד בעולם שבו native SDK, P/Invoke ו-Win32 API מתערבבים כדבר שבשגרה — כמו באפליקציית בקרת ציוד — ההתאמה טובה.

שני התפקידים של Application VerifierApplication Verifier מזהה misuse בגבול native, וגם חושף מוקדם failure path נדיר. אפשר לזהות invalid handle ו-heap corruption, ולהזריק מצב שמדמה מחסור בזיכרון.Application Verifierזיהוי misuse בגבול nativeחשיפה מוקדמת של failure path נדירinvalid handle או heap corruptioninjection של מצב שמדמה מחסור בזיכרון

איור 1: ל-Application Verifier יש שני תפקידים: זיהוי misuse, וחשיפה מוקדמת של failure path.

2. מה זה Application Verifier

2.1. בקצרה

Application Verifier הוא כלי runtime verification לאפליקציות user-mode ב-Windows. הוא מנטר בזמן ריצה איך האפליקציה משתמשת ב-OS API ואיך היא מנהלת משאבים, מזהה שימוש חשוד, ואפשר גם להזריק כשל בכוונה.

בניגוד ל-static analysis או unit tests, זה כלי שמראה איך הקוד נשבר בפועל כשעוברים ב-code path הזה. לכן הוא מתאים לחשוף failure path שלא רואים בבדיקות פונקציונליות רגילות.

test harnessאפליקציית בקרה / SDK wrapperApplication VerifierWin32 API / native DLL / משאבי OSverifier stopdebugger outputAppVerifier logsstructured log פנימי

איור 2: ה-harness מריץ את אפליקציית הבקרה, Application Verifier מנטר אותה, והתוצאות נשארות כ-verifier stop, פלט debugger ולוגים.

2.2. מתי זה באמת עוזר

זה עוזר במיוחד במצבים כאלה:

  • קוראים ל-native DLL או ל-SDK של מצלמה
  • חוצים גבול של P/Invoke או COM
  • משתמשים הרבה, במישרין או בעקיפין, ב-handle, heap, lock ו-virtual memory
  • ב-happy path כמעט אף פעם לא קורס, אבל נראה שניהול ה-lifetime מתפרק דווקא ב-failure path
  • “לפעמים מחזיר כשל מוזר” מופיע לפני “קורס”

מצד שני, זה לא כלי למעקב אחרי object graph בעולם managed טהור. לכן גם באפליקציית C#, אם הגבול מול native SDK או Win32 עבה, זה יעיל מאוד. אבל זה לא כלי שרואה לבד את כל ה-managed heap leak.

מתי Application Verifier עוזר ומתי לאאם הבעיה ב-native SDK או בגבול Win32, Application Verifier עוזר. object graph של managed טהור הוא מחוץ לתחום, וצריך כלי אחר.native SDK או גבול Win32object graph של managed טהורבאיזו שכבה הבעיהApplication Verifier עוזרמחוץ לתחום (כלי אחר)

איור 3: קו הגבול הוא עובי שכבת ה-native / Win32. זה לא כלי שרואה לבד את עולם ה-managed הטהור.

2.3. למה זה שימושי בפועל

בפועל, שלושת היתרונות המרכזיים הם בערך אלה:

  1. אפשר לעצור מוקדם misuse בגבול ה-native
    • invalid handle
    • heap corruption
    • lock misuse
    • שימוש שגוי ב-API של virtual memory, ועוד
  2. אפשר לחשוף מוקדם שבירה שמופיעה רק במחסור במשאבים
    • המקבילה ל-malloc נכשלת מדי פעם
    • CreateEvent או CreateFile נכשלים מדי פעם
    • VirtualAlloc נכשל
  3. קל לעקוב בשילוב עם debugger
    • !avrf
    • !htrace
    • !heap -p -a
    • לוג של verifier stop

מה שמקשה באפליקציית בקרת ציוד הוא “לא לדעת מה קרה ב-failure path”. Application Verifier מצמצם את חוסר הבהירות הזה.

שלושה יתרונות בפועלאפשר לעצור misuse מוקדם, לחשוף מוקדם שבירה שמופיעה רק במחסור במשאבים, ולעקוב בקלות בשילוב עם debugger.Application Verifierעצירה מוקדמת של misuseחשיפה מוקדמת של אופן השבירהקל לעקוב עם debuggerהרחבות כמו avrf ו-htrace

איור 4: בפועל זה מתכנס לשלושה דברים: זיהוי מוקדם, חשיפה מוקדמת של failure path, ושילוב עם debugger.

2.4. התקנה והפעלה מקומית

קודם סוגרים את ההתקנה. בלי זה, כל מה שאחר כך נשאר תיאורטי.

Application Verifier מגיע כחלק מ-Windows SDK. הוא לא מותקן עם Windows עצמו, אז מריצים את ה-installer של ה-SDK ומסמנים את התיבה “Application Verifier” במסך בחירת הרכיבים. שם קובץ ההרצה הוא appverif.exe.

יש שלוש דרישות קדם:

  • המשתמש שמריץ צריך להיות חבר בקבוצת Administrators על אותה מכונה
  • ARM64EC לא נתמך
  • היעד לאימות הוא קוד unmanaged (native)

היחס בין ה-GUI לשורת הפקודה פשוט, וזה מונע בלבול:

  מה זה עושה
GUI (appverif.exe) כותב ל-Registry את שם ה-EXE היעד ואת שילוב הבדיקות שמדליקים
שורת פקודה (appverif -enable ...) כותב בפקודה בדיוק את אותה הגדרת Registry
בזמן ריצה כשה-EXE היעד עולה, ה-DLL של ה-verifier נטען לפי ההגדרה, ונכנס hook ל-Win32 API

כלומר בשתי הדרכים עושים אותו דבר. בדרך כלל GUI בפעם הראשונה הידנית, ושורת פקודה ב-CI או בסקריפט.

הקשר בין GUI לשורת הפקודהגם GUI וגם שורת הפקודה רק כותבים את אותה הגדרת Registry. כשה-EXE היעד עולה, ה-DLL של ה-verifier נטען לפי ההגדרה ונכנס hook ל-Win32 API.GUI (appverif.exe)כתיבת ההגדרה ל-Registryשורת פקודהקריאה בהפעלת ה-EXE היעדטעינת DLL של verifier ו-hook

איור 5: גם GUI וגם שורת הפקודה רק כותבים את אותה הגדרת Registry. ה-hook נכנס רק בהפעלת ה-EXE היעד.

ב-GUI: קליק ימני על אזור Applications משמאל, “Add Application”, מוסיפים את ה-EXE היעד, מסמנים למשל Basics באזור Tests מימין, ולוחצים “Save”. לביטול: קליק ימני על אותו אזור Applications, “Delete Application”, ואז “Save”.

מכאן נובעות שתי מגבלות חשובות:

  • אי אפשר להפעיל את זה על process שכבר רץ. ה-hook נכנס בזמן טעינת ה-DLL, לכן הסדר הוא להגדיר ואז להפעיל.
  • ההגדרה נשארת עד שמוחקים אותה במפורש. אם משאירים אותה אחרי “ניסיתי פעם אחת”, אותו EXE ימשיך לעלות תמיד תחת ה-verifier על אותה מכונה.

לוג הזיהוי נשמר כברירת מחדל בפורמט בינארי תחת %USERPROFILE%\AppVerifierLogs, ואפשר להמיר אותו ל-XML מה-GUI או משורת הפקודה כדי לרכז אותו.

סדר ההפעלה ומה נשאר אחריהה-hook נכנס בזמן טעינת ה-DLL, לכן אי אפשר להוסיף אותו ל-process שכבר רץ. מגדירים ואז מפעילים, וההגדרה נשארת עד שמוחקים אותה במפורש.כותבים את ההגדרהמפעילים את ה-EXE היעדרץ תחת verifierההגדרה נשארת עד שמוחקיםאם משאירים, תמיד עולה תחת verifierprocess שכבר רץאי אפשר להפעיל בדיעבד

איור 6: אי אפשר לשבור את הסדר “מגדירים ואז מפעילים”, וההגדרה נשארת על המכונה עד שמוחקים אותה במפורש.

3. מה אפשר לעשות עם Application Verifier

3.1. Basics: Handles / Heaps / Locks / Memory / TLS ועוד

הסט הבסיסי של Application Verifier הוא Basics. כאן מרוכזות הבדיקות שמשתמשים בהן הרבה בפועל.

שכבה מה נבדק שימוש בהקשר הזה
Handles שימוש ב-invalid handle האם דורכים על handle שכבר נסגר / פגום
Heaps heap corruption לחשוף buffer overrun או use-after-free בגבול ה-native SDK
Leak משאבים שלא שוחררו עד unload של ה-DLL בדיקת harness קצר-חיים, או מקרים שכוללים unload
Locks / SRWLock lock misuse בדיקת race בין reconnect ל-shutdown
Memory שימוש שגוי ב-VirtualAlloc / MapViewOfFile וכדומה בדיקת חריגות סביב buffer גדול או shared memory
TLS שימוש שגוי ב-API של Thread Local Storage רשת ביטחון לקוד native עם גבולות thread מורכבים
Threadpool עקביות של threadpool API ומצב ה-worker עזר כשיש הרבה callback או עיבוד async

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

הרעיון מאחורי הזיהוי המוקדם של Basicsבמקום לקרוא לוגים אחרי קריסה ולנחש, Basics עוצר שימוש חשוד במקום, וכך תקלה של הרצה ארוכה נחשפת מוקדם.שימוש חשוד ב-APIקבוצת הבדיקות של Basicsעצירה במקוםהבעיה נחשפת מוקדםבעבר רק קוראים אחרי הקריסה

איור 7: הערך של Basics הוא להחליף “קריאה אחרי הקריסה” ב”עצירה במקום”.

3.2. Low Resource Simulation: לחשוף מחסור בזיכרון ובמשאבים בלי לחכות

זה החלק שבאמת נוח בפועל. כי אפשר לגרום לתופעה שמדמה מחסור בזיכרון או במשאבים, בלי באמת לרוקן את ה-RAM.

הרעיון פשוט:

  • קריאה ל-API מסוים
  • בהסתברות קבועה
  • נכשלת בכוונה

כך עוברים ב-error path שבדרך כלל כמעט אף פעם לא דורכים בו.

באופן קונקרטי, קל לגרום בכוונה לתופעות כאלה:

  • HeapAlloc או VirtualAlloc נכשלים
  • CreateFile נכשל
  • CreateEvent נכשל
  • MapViewOfFile נכשל
  • הקצאת OLE/COM כמו SysAllocString נכשלת

זה נוח בהרבה מלנסות לגרום למחסור אמיתי בזיכרון ולסחוב את כל המכונה איתו. בנוסף, אפשר לכוון fault injection רק ל-DLL ספציפי. במבנה שבו wrapper פנימי מתערבב עם vendor SDK, כמו באפליקציית בקרת ציוד, זה מתאים מאוד לעבודה בפועל.

איך Low Resource Simulation עובדקריאת API מסוג מסוים נכשלת בכוונה בהסתברות קבועה, וכך עוברים ב-error path שבדרך כלל לא דורכים בו. אפשר גם לצמצם את היעד ל-DLL ספציפי.כןלאקריאת APIפגע בהסתברות?מחזירים כשל בכוונהעיבוד רגילמעבר ל-error path נדיראפשר לצמצם ל-DLL ספציפי

איור 8: Low Resource Simulation הוא fault injection שגורם לקריאת API להיכשל בהסתברות קבועה.

3.3. Page Heap ו-debugger

כדי לראות heap corruption, השילוב של Heaps עם page heap חזק. full page heap במיוחד משתמש ב-guard page, והיתרון הוא שקל לעצור ברגע ההשחתה עצמו.

אבל זה כבד למדי. במקום ריצה כוללת וממושכת, נוח יותר לצמצם לתרחיש שקרוב לשחזור ולהריץ תחת debugger.

לכן בפועל החלוקה הזו ריאלית:

  • קודם מפעילים Basics על טווח רחב
  • אם ה-heap נראה חשוד, עוברים ל-full page heap
  • אם זה כבד מדי, יורדים ל-light page heap
  • בבדיקה ארוכה ברמת ייצור, מסתכלים בעיקר על לוגים פנימיים

בסוף, AppVerifier הוא לא כלי קסם אלא כלי שבו בוחרים מצב לפי הסיטואציה.

איך בוחרים page heapקודם Basics על טווח רחב. אם ה-heap חשוד, full page heap שעוצר ברגע ההשחתה. אם זה כבד מדי, יורדים ל-light page heap. בדיקה ארוכה ברמת ייצור מסתמכת בעיקר על לוגים פנימיים.כןלאכןלאBasics על טווח רחבה-heap חשוד?עצירה עם full page heapבדיקה ארוכה בעיקר בלוגים פנימייםזה כבד מדי?ירידה ל-light page heapשחזור מקומי תחת debugger

איור 9: page heap הוא לא כלי שמדליקים תמיד. קודם Basics על טווח רחב, ואז מחליפים מצב לפי הסיטואציה.

3.4. !avrf / !htrace / לוגים

קודם מונח אחד. verifier stop, שכבר הופיע כמה פעמים, הוא אירוע זיהוי ש-Application Verifier מוציא כשהוא קובע שהשימוש הזה לא תקין. זו לא סתם שורת לוג: אם רצים תחת debugger, זה עושה break במקום. לכל stop יש מספר, והוא מוצג בצורה כמו VERIFIER STOP 00000300. יש stop שאפשר להמשיך אחריו, ויש stop שאי אפשר להמשיך אחריו (חייבים לסיים את ה-process).

Application Verifier לא נעצר אחרי ה-stop. יש debugger extensions ולוגים, ולכן קל יותר לעקוב אחרי מה שקרה.

  • !avrf
    • רואים את הגדרות ה-verifier הנוכחיות ואת ה-stop שקורה עכשיו
  • !htrace
    • רואים את ה-stack של open / close / invalid reference של handle
  • !heap -p -a
    • בשילוב עם page heap, עוקבים אחרי בלוק heap פגום
  • לוג AppVerifier
    • שומר את הלוג שנוצר בזמן ה-stop

במיוחד כשמדליקים Handles, נוח ש-handle tracing נדלק אוטומטית. כך קל אחר כך לראות איפה ה-handle הזה נפתח ואיפה הוא נסגר.

מ-verifier stop עד החקירהזיהוי שימוש לא תקין מוציא verifier stop עם מספר. תחת debugger זה עושה break במקום. ב-avrf בודקים הגדרות ו-stop, וב-htrace את היסטוריית ה-handle.זיהוי שימוש לא תקיןverifier stop (עם מספר)break במקום תחת debuggeravrf לבדיקת הגדרות ו-stophtrace לבדיקת היסטוריית handleיש stop שאפשר להמשיך ממנו ויש שלא

איור 10: verifier stop הוא לא סתם שורת לוג. תחת debugger הוא עושה break במקום והופך לנקודת התחלה לחקירה.

4. למה הכנסנו את זה הפעם

4.1. המטרה היא לא רק למצוא באג

המטרה הפעם לא הייתה סתם “למצוא באג אחד עם AppVerifier”. בניסוח מעשי יותר, מה שרצינו לבדוק היה:

  • אם בעתיד תהיה דליפת משאב ב-failure path אחר
  • האם ההקשר נשאר בלוג כמו שצריך
  • האם אפשר לעקוב עד הסוף יחד עם מידע מה-debugger
  • שהמצב לא יהפוך ל”לא ברור מה קרה”

כלומר, השתמשנו בזה לא רק כגלאי, אלא כבדיקה של תשתית התצפית.

4.2. לגרום למצב שמדמה מחסור בזיכרון

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

לכן השתמשנו ב-Low Resource Simulation כדי לדרוך בכוונה על ה-failure path שסביר שיופיע במחסור בזיכרון או במשאבים.

כך קל יותר לענות על שאלות כאלה:

  • אם CreateEvent נכשל, האם cameraId ו-phase נשארים בלוג
  • האם cleanup רץ כמו שצריך אחרי אתחול חלקי
  • אם VirtualAlloc נכשל, האם ה-retry לא נשבר
  • האם ה-handle חוזר כש-CreateFile נכשל בנתיב השמירה

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

בדיקת תשתית התצפית עם fault injectionדורכים בכוונה על כשלים עם Low Resource Simulation כדי לבדוק אם ההקשר נשאר בלוג, אם cleanup רץ, ואם retry לא נשבר. המטרה היא לא לגרום לכשל, אלא לוודא שאפשר לקרוא איך זה נשבר.דריכה בכוונה על כשלהאם ההקשר נשאר בלוגהאם cleanup רץהאם retry לא נשברמצב שבו אפשר לקרוא איך זה נשבר

איור 11: המטרה של fault injection היא לא לגרום לכשל, אלא לבדוק שאפשר לקרוא איך זה נשבר כשהכשל קורה.

4.3. לבדוק שאפשר לעקוב אחרי handle misuse כשזה קורה

כמו ה-handle leak בחלק הראשון, גם סביב handle המקום שבו קורסים בסוף נוטה להיות שונה מהגורם האמיתי.

לכן רצינו לוודא:

  • כשיוצא invalid handle stop, האם אפשר לעקוב ב-!htrace אחרי open / close
  • האם זה מתחבר ל-resourceId / sessionId / phase בלוגים הפנימיים
  • האם Handle Count חוזר אחרי כשל
  • כשה-harness הוא process קצר-חיים, האם קל לראות את ה-diff של ה-leak

כשמגיעים עד כאן, עוברים מסתם “יצא באג” עד “באיזו אחריות ניהול ה-lifetime התפרק”.

בדיקה שאפשר לעקוב אחרי handle misuseכשיוצא invalid handle stop, בודקים אם htrace עוקב אחרי open ו-close, אם זה מתחבר להקשר בלוגים הפנימיים, ואם Handle Count חוזר. משם מגיעים לזיהוי האחריות שבה ניהול ה-lifetime התפרק.invalid handle stopמעקב open ו-close עם htraceחיבור להקשר בלוגים הפנימייםבדיקה ש-Handle Count חוזרזיהוי האחריות שבה ניהול ה-lifetime התפרק

איור 12: בבעיות handle לא עוצרים ב”יצא באג”. בודקים שאפשר להגיע עד האחריות שבה ניהול ה-lifetime התפרק.

5. איך גורמים למצב שמדמה מחסור בזיכרון או במשאבים

5.1. איך Low Resource Simulation עובד

Low Resource Simulation הוא בעצם fault injection. זה פחות שחזור מדויק של סביבת low-resource, ויותר ערבוב מלאכותי של כשלי API אופייניים שקורים במחסור במשאבים.

לכן מקומות השימוש די ברורים:

  • בדיקת cleanup ב-failure path
  • בדיקת חוזק של retry / reconnect
  • בדיקת אתחול שבו הצלחה חלקית מתערבבת עם כשל חלקי
  • לוודא שגם “כשל שבדרך כלל לא קורה” נשאר בלוג

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

איך מצמצמים fault injectionאם מכשילים הכול מההתחלה הלוג מתפוצץ ולא ברור על מה מסתכלים. לכן פותחים קודם רק כשלים שקרובים ל-failure path שרוצים לראות.הכול נכשל מההתחלההלוג מתפוצץ ובלתי קריאפותחים רק את הכשלים שרוציםברור על מה מסתכלים

איור 13: ב-fault injection לא פותחים הכול בבת אחת. מצמצמים לכשלים שקרובים ל-failure path שרוצים לראות.

5.2. אילו API אפשר להכשיל

ב-Low Resource Simulation אפשר לגרום באופן טיפוסי לסוגי ה-API הבאים להיכשל בהסתברות:

סוג דוגמה דוגמה באפליקציית בקרת ציוד
Heap_Alloc הקצאת heap buffer זמני, metadata של תמונה, הקצאה פנימית ב-wrapper של ה-SDK
Virtual_Alloc הקצאת virtual memory frame buffer גדול, ring buffer
File CreateFile וכדומה פתיחת נתיב שמירה או קובץ לוג
Event CreateEvent וכדומה התראת frame ready, סנכרון stop/reconnect
MapView CreateMapView וכדומה shared memory או memory mapped file
Ole_Alloc SysAllocString וכדומה גבול COM / OLE
Wait משפחת WaitForXXX סביב כשל בהמתנה סינכרונית
Registry גישה ל-Registry קריאה וכתיבה של הגדרות, או הגדרות סביב דרייבר

בפועל, במקום לפתוח הכול בבת אחת, החשוב הוא לצמצם ולפתוח קודם את מה שקרוב ל-failure path שרוצים לראות הפעם.

5.3. איך מיישמים את זה בפועל

כדוגמה לשורת פקודה, זה נראה בערך כך:

appverif /verify CameraHarness.exe
appverif /verify CameraHarness.exe /faults
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 virtual_alloc=20000 file=20000 event=20000
appverif -query lowres -for CameraHarness.exe

אין טעם להעתיק פקודות בלי להבין מה הן עושות, אז שורה-שורה:

פקודה מה היא עושה
appverif /verify CameraHarness.exe מדליקה את קבוצת הבדיקות של Basics עבור CameraHarness.exe
appverif /verify CameraHarness.exe /faults בנוסף, מדליקה fault injection. אבל היעד מוגבל ל-OLE_ALLOC ו-HEAP_ALLOC בלבד
appverif -enable lowres -for CameraHarness.exe -with heap_alloc=20000 ... מדליקה את lowres (Low Resource Simulation) ומציינת בנפרד את סוג ה-API שנכשל ואת ההסתברות
appverif -query lowres -for CameraHarness.exe מציגה מה מוגדר עכשיו ובאיזו הסתברות
appverif /n CameraHarness.exe מוחקת את ההגדרה של אותו EXE (אותה מטרה כמו -disable * -for או -delete settings -for)

גם איך קוראים את הארגומנטים:

  • ההסתברות היא ביחידות של אחד למיליון. אפשר לציין מספר שלם בין 0 ל-1,000,000, ו-20000 הוא 20000 / 1,000,000, כלומר 2%. זה לא “פעם ב-20 אלף”. גם בתיעוד של Microsoft מופיעה הדוגמה -with registry=20000 file=20000 שגורמת ל-API של Registry ושל קבצים להיכשל ב-2%.
  • אחרי /faults אפשר לרשום הסתברות, grace period ושמות DLL. התחביר הוא /faults [הסתברות [grace period ב-ms [DLL ...]]]. אם משמיטים את ההסתברות ברירת המחדל היא 5%, ואם משמיטים את ה-grace period ברירת המחדל היא 500 ms. grace period פירושו “לא להזריק fault בפרק הזמן הזה מתחילת ה-process”, כדי למנוע מצב שבו ה-startup עצמו נכשל ואי אפשר לבדוק כלום.
  • /n הוא ביטול. אפשר לחשוב על n בערך כ-“no verifier”. זו הפקודה המקבילה שמונעת להשאיר את זה דלוק לתמיד.

פלט -query lowres חוזר בערך בצורה הזו. כאן בודקים שההסתברות שהוגדרה באמת נכנסה, ושלא נפתחו בטעות סוגים שלא כיוונו אליהם.

Settings for CameraHarness.exe:
Test [lowres] enabled.

Include = *
Exclude =
TimeOut = 2000 (0x7D0)
WAIT = 0 (0x0)
HEAP_ALLOC = 20000 (0x4E20)
VIRTUAL_ALLOC = 0 (0x0)
REGISTRY = 0 (0x0)
FILE = 20000 (0x4E20)
EVENT = 20000 (0x4E20)
MAP_VIEW = 0 (0x0)
OLE_ALLOC = 0 (0x0)
STACKS = false

Include ו-Exclude הם צמצום מודולי היעד, ו-TimeOut הוא הזמן אחרי ההפעלה שבו לא מזריקים fault. ברירת המחדל משתנה לפי איך מכניסים את ההגדרה, אז עדיף לבדוק בפלט הזה ולא להניח.

דרך החשיבה היא בערך כזו:

  1. קודם מריצים happy path רק עם Basics
  2. אחר כך מוסיפים Low Resource Simulation ומריצים עם fault injection
  3. במידת הצורך נותנים הסתברות רק לכשלים שרוצים לראות, כמו file או event
  4. אם רוצים לכוון ל-DLL ספציפי, מצמצמים אליו בלבד

הקיצור /faults נוח, אבל לבד הוא ממקד בעיקר ב-OLE_ALLOC ו-HEAP_ALLOC. אם רוצים לראות את ה-failure path של CreateFile או CreateEvent, בטוח יותר לכתוב עד -enable lowres -with file=... event=....

באפליקציית בקרת ציוד, לרוב קריא יותר לצמצם ל-DLL של ה-camera wrapper או לנתיב השמירה, במקום לפזר fault על כל האפליקציה.

הסדר להפעלת fault injectionקודם happy path רק עם Basics, אחר כך מוסיפים Low Resource Simulation ומריצים עם fault injection, נותנים הסתברות רק לכשלים שרוצים, ובמידת הצורך מצמצמים ל-DLL ספציפי.happy path רק עם Basicsמוסיפים Low Resource ומריציםהסתברות רק לכשלים שרוציםמצמצמים ל-DLL ספציפי

איור 14: לא יורים ישר למטרה. מתקדמים בהדרגה מ-happy path עם Basics, ואז מצמצמים את ה-fault injection.

גם הכתיבה הקונקרטית של “צמצום ל-DLL”: הארגומנט השלישי ואילך של /faults הוא ציון מודול היעד.

appverif /verify CameraHarness.exe /faults 50000 1000 CameraSdkWrapper.dll

כך, כשמפעילים את CameraHarness.exe, אחרי שעוברות 1000 ms מההפעלה, רק פעולות שהתחילו מ-CameraSdkWrapper.dll נכשלות בהסתברות 5% (50000 / 1,000,000). כותבים את שם המודול כולל הסיומת, בלי נתיב. אפשר לציין לא רק .dll, אלא גם מודולים נטענים אחרים כמו .ocx.

בודקים אם הצמצום באמת עובד בשורות Include ו-Exclude של appverif -query lowres -for CameraHarness.exe. אם זה עדיין *, כל ה-process עדיין היעד.

fault injection מצומצם ל-DLLכשמציינים הסתברות, grace period ומודול יעד בארגומנטים של faults, אחרי שה-grace period מההפעלה עובר, רק פעולות שהתחילו מה-DLL שצוין נכשלות בהסתברות שצוינה. מאמתים את הצמצום ב-Include ו-Exclude של query.מציינים הסתברות, grace period ושם DLLבלי injection ב-grace period מההפעלהנכשלות רק פעולות שהתחילו מה-DLL שצויןאימות ב-Include ו-Exclude של query

איור 15: fault injection שמצומצם ל-DLL מכשיל, רק אחרי ה-grace period, פעולות שהתחילו מהמודול שצוין.

אם רצים תחת debugger, אפשר גם לשנות את הטווח באמצע.

!avrf -trg dll CameraSdkWrapper.dll
!avrf -skp dll VendorSdk.dll

-trg הוא “לכוון לכאן”, ו--skp הוא “לדלג על זה”. אפשר גם לבדוק את הגדרות ה-fault injection הנוכחיות עם !avrf -flt, או לראות את ה-stack של הכשלים שהוזרקו לאחרונה עם !avrf -flt stacks 10.

למשל אפשר לבנות תרחישים כאלה:

  • כשל CreateEvent מיד אחרי תחילת reconnect
  • כשל CreateFile בתחילת השמירה
  • כשל בהקצאת buffer זמני
  • כשל SysAllocString בהמרת COM
  • בדיקת מסלול הכשל ב-API של המתנה

בבדיקות happy path רגילות כמעט אי אפשר לדרוך על התרחישים האלה. דווקא לכן יש ערך לדרוך עליהם בכוונה.

6. איך בודקים בעיות handle

6.1. בדיקת Handles

סביב handle, קודם משתמשים ב-Handles. זה מקל לזהות שימוש ב-invalid handle.

התקלות האופייניות שזה תופס:

  • שימוש חוזר ב-handle שכבר נסגר
  • העברת ערך handle פגום
  • שימוש ב-handle שלא אותחל בגלל כשל באמצע
  • ה-lifetime התפרק ו-thread אחר נוגע ב-handle

בהרצה ארוכה, מה שנראה בדרך כלל רק כ”לפעמים יוצאת שגיאה מוזרה” עשוי, תחת ה-verifier, לעצור במקום. החשיפה המוקדמת הזו מאוד עוזרת.

תקלות שבדיקת Handles תופסתתחת ה-verifier אפשר לעצור במקום תקלות כמו שימוש חוזר ב-handle סגור, ערך handle פגום, handle לא מאותחל בגלל כשל באמצע, ו-misuse מ-thread אחר אחרי ש-lifetime התפרק.שימוש חוזר ב-handle סגורverifier stop במקוםערך handle פגוםhandle לא מאותחלmisuse בגלל lifetime שהתפרק

איור 16: בהרצה ארוכה תקלה שהייתה נראית רק כ”שגיאה מוזרה מדי פעם” — בדיקת Handles עוצרת אותה במקום.

6.2. stack של open / close עם !htrace

מה שנוח ב-Handles הוא שהוא משתלב טוב עם handle tracing.

מכאן משתמשים ב-debugger, אז קודם מתקינים WinDbg. הוא מופץ כחלק מ-Debugging Tools for Windows, ואפשר להתקין אותו מאותו installer של Windows SDK כמו Application Verifier.

windbg -xd av -xd ch -xd sov CameraHarness.exe
!avrf
!htrace 0x00000ABC

האופציות בשורה הראשונה הן לא נוסחת קסם. יש שלושה סוגי exception ש-Application Verifier זורק בזמן זיהוי:

אופציה exception מתי זה יוצא
av access violation (0xC0000005) כשמזוהה חריגה מגבולות buffer ב-heap
ch invalid handle (0xC0000008) כשמזוהה שימוש ב-invalid handle
sov stack overflow (0xC00000FD) כשנקבע שה-stack ההתחלתי לא מספיק

ו--xd אומר לתפוס את אותו exception ב-second chance. הסיבה: Application Verifier עצמו מטפל ב-first chance ובונה את מידע ה-stop, ולכן לא נוח שה-debugger יקפוץ לפני כן. אם ה-debugger כבר רץ, זו אותה הגדרה כמו sxd av, sxd ch, sxd sov.

מה שרוצים לראות עם !htrace הוא בערך:

  • איפה ה-handle נפתח
  • איפה הוא נסגר
  • האם נעשתה אליו פנייה כ-invalid handle
  • האם הצטברו יותר פעולות open ממה שציפו

גם איך זה נראה בפועל. הפלט הבא הוא דוגמה מהתיעוד הרשמי, לא מהסביבה שלנו, אבל הצורה זהה.

כשדורכים על invalid handle, קודם יוצא כך:

Invalid handle - code c0000008 (first chance)
===================================================
VERIFIER STOP 00000300: pid 0x558: invalid handle exception for current stack trace
        C0000008 : Exception code.
        0012FBF8 : Exception record. Use .exr to display it.
        0012FC0C : Context record. Use .cxr to display it.
        00000000 :
===================================================
This verifier stop is continuable.
After debugging it use 'go' to continue.
===================================================

אם אחר כך מריצים !avrf, מוצג מה דלוק עכשיו ואיזה stop קורה. השורה האחרונה היא הסיכום.

0:000> !avrf
Global flags: 00000100
Application verifier global flag is set.
Application verifier settings (00000004):
   - no heap checking enabled!
   - handle checks
Page heap is not active for this process.
Current stop 00000300 : c0000008 0012fbf8 0012fc0c 00000000 .
    Using an invalid handle (either closed or simply bad).

אם מסתכלים על היסטוריית ה-handle הזה עם !htrace, מוצגים OPEN / CLOSE / BAD REFERENCE, כל אחד עם ה-stack שלו.

0:000> !htrace 7DC

--------------------------------------
Handle 0x7DC - BAD REFERENCE:
0x801902BE: ntoskrnl!NtSetEvent+0x6C
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - CLOSE:
0x801E1EDD: ntoskrnl!NtClose+0x19
0x010012C1: badhandle!mainCRTStartup+0xE3
--------------------------------------
Handle 0x7DC - OPEN:
0x77DE265C: KERNEL32!CreateEventA+0x66
0x010011A0: badhandle!main+0x20
--------------------------------------

אופן הקריאה פשוט: אם אחרי CLOSE מגיע BAD REFERENCE, זה אומר שנעשה שימוש חוזר ב-handle שכבר נסגר. ב-stack של OPEN רואים גם איפה אותו handle נוצר.

מה שמקשה ב-handle leak או handle misuse הוא שה-API שנכשל בסוף אינו הגורם האמיתי. עם !htrace אפשר לעקוב די בפירוט אחרי היסטוריית ה-handle הזה.

איך קוראים היסטוריית handle ב-htracehtrace מציג OPEN, CLOSE ו-BAD REFERENCE כל אחד עם stack. אם אחרי CLOSE מגיע BAD REFERENCE, זה שימוש חוזר ב-handle סגור. ה-stack של OPEN מראה איפה הוא נוצר.OPEN (איפה נוצר)CLOSE (איפה נסגר)BAD REFERENCEמתברר שזה שימוש חוזר ב-handle סגורלכל רשומה מצורף stack

איור 17: הקריאה של htrace פשוטה: אם אחרי CLOSE מופיע BAD REFERENCE, זה שימוש חוזר ב-handle סגור.

6.3. שילוב עם לוגים פנימיים

עם זאת, Application Verifier לבדו לא מספיק. במיוחד, לעשות את כל חקירת ה-leak של EXE שרץ לאורך זמן רק איתו זה די מתיש.

לכן בפועל משלבים את אלה:

  • Handle Count תקופתי
  • sessionId
  • resourceId
  • phase
  • lifecycle log של create/open מול close/dispose
  • dump ופלט debugger בזמן verifier stop

כך אפשר לעקוב, למשל, כך:

  1. ב-heartbeat מזהים ש-trend של Handle Count חשוד
  2. ב-lifecycle log מצמצמים משאבים שיש להם Create בלי Close
  3. בהרצת verifier מוציאים מוקדם invalid handle או misuse
  4. ב-!htrace רואים את ה-stack של open / close

השילוב הזה מקל מאוד על המעקב.

סדר השילוב בין לוגים פנימיים ל-verifierheartbeat מגלה trend חשוד ב-Handle Count, lifecycle log מצמצם משאבים בלי Close, הרצת verifier חושפת misuse מוקדם, ו-htrace מציג את ה-stack של open ו-close, בסדר הזה.מזהים trend חשוד ב-Handle Countמצמצמים משאבים עם lifecycle logחושפים misuse מוקדם בהרצת verifierרואים stack עם htrace

איור 18: חלוקת עבודה שבה הלוגים הפנימיים מזהים trend וה-verifier מזהה misuse, מחוברת בסדר הזה.

7. איך בונים תשתית לבדיקות failure path

7.1. להריץ דרך harness, לא דרך אפליקציית הייצור

אי אפשר להפעיל את Application Verifier על process שכבר רץ. מגדירים ואז מפעילים.

בנוסף, ההגדרה נשארת עד שמוחקים אותה במפורש. לכן בפועל נוח יותר לעבוד עם harness EXE ייעודי לבדיקות, לא עם אפליקציית הייצור עצמה.

למשל מבנה כזה:

Scenario RunnerCameraHarness.exeCameraSdkWrapper.dllVendor SDKStructured LogDump / Debugger

איור 19: מבנה harness שמריץ תרחיש אחד לכל process. היעד של ה-verifier הוא לא ה-DLL, אלא ה-harness EXE שמריץ אותו.

כך מקבלים:

  • אפשר להריץ תרחיש אחד לכל process
  • קל לראות diff של leak
  • קל להדליק ולכבות הגדרת AppVerifier
  • גם בדיקת DLL אפשר לטפל בה מצד ה-EXE

הפקודות נראות כך:

appverif /verify CameraHarness.exe
appverif /n CameraHarness.exe

/verify מדליק Basics, ו-/n מוחק את ההגדרה (ראו גם את הטבלה בסעיף 5.3). ההפעלה לפני ה-startup, והביטול במפורש. כשמריצים את זה מתוך הנחת יסוד של harness, גם קל יותר להפחית תקלות הגדרה.

7.2. לפצל את תפריט הבדיקות

בתשתית לבדיקות failure path עדיף לא לעשות הכול בבת אחת. בערך חלוקה לשלושה מסלולים כאלה קלה לקריאה:

  1. happy path + Basics
    • לא מזריקים שום כשל
    • מוודאים שלא יוצא verifier stop
  2. מסלול fault injection
    • Low Resource Simulation
    • מכשילים בכוונה event / file / heap_alloc / virtual_alloc וכדומה
  3. מסלול העמקה ב-heap
    • Heaps
    • full page heap
    • שחזור מקומי תחת debugger

כשמפרידים את זה, פחות מתבלבלים בין “האם זה נשבר בשימוש הרגיל” לבין “האם זה נשבר רק במחסור במשאבים”.

במיוחד, נוכחות או היעדר fault injection משנה משמעותית את ה-code path שעוברים בו. לכן כדאי להריץ גם run בלי fault וגם run עם fault.

תפריט בדיקות בשלושה מסלוליםמחלקים את הבדיקות ל-happy path פלוס Basics שבו מוודאים שלא יוצא stop, למסלול fault injection עם Low Resource Simulation שמכשיל בכוונה, ולמסלול העמקה ב-heap שמריץ full page heap תחת debugger לשחזור מקומי.איך מריצים בדיקות failure pathhappy path ו-Basicsמסלול fault injectionהעמקה ב-heapמוודאים שלא יוצא stopמזריקים את הכשל שרוציםשחזור מקומי תחת debugger

איור 20: במקום הכול בבת אחת, חלוקה לשלושה מסלולים מונעת בלבול לגבי איפה זה נשבר.

7.3. מה אוספים

כמינימום, כדאי לתעד את אלה:

סוג מה רוצים
לוג האפליקציה cameraId, sessionId, phase, handleCount, error code
מצב ה-process Handle Count, Private Bytes, Thread Count
מידע debugger !avrf, !htrace, ובמידת הצורך !heap -p -a
dump בזמן verifier stop, או בסיום לא תקין
לוג AppVerifier תיעוד ה-stop, ובמידת הצורך המרה ל-XML לריכוז

במידת הצורך אפשר להמיר גם את לוג AppVerifier ל-XML ולרכז אותו. אבל לרוב הסתכלות רק עליו לא סוגרת את הגורם, לכן בפועל קוראים אותו לצד הלוגים הפנימיים.

הרבה לוג לבדו לא שווה הרבה. החשוב הוא שאחר כך אפשר לחבר סיבתיות.

7.4. תנאי pass

גם תנאי ה-pass חלש אם הוא רק “לא קרס”. בהקשר הזה נדרשו לפחות אלה:

  • לא יוצא verifier stop ב-happy path + Basics
  • גם עם fault injection, הכשל שציפינו לו נשאר בלוג
  • משאבים שאותחלו רק חלקית מתנקים כמו שצריך
  • אחרי reconnect / retry, Handle Count חוזר קרוב ל-baseline
  • כשיוצא verifier stop, אפשר לעקוב לפי sessionId / phase וה-stack
  • הכשל לא הופך ל”לא ברור מה קרה”

החשוב כאן הוא להעריך בנפרד את “לא נשבר” ואת “אפשר לעקוב כשזה נשבר”.

שני הצירים של תנאי passתנאי pass מחולק לציר לא נשבר, שבו לא יוצא stop ומשאבים מתנקים, ולציר אפשר לעקוב כשזה נשבר, שבו הכשל נשאר בלוג ואפשר לעקוב עם ה-stack.תנאי passלא נשבראפשר לעקוב כשזה נשברלא יוצא stopהמשאבים מתנקיםהכשל נשאר בלוגאפשר לעקוב עם ה-stack

איור 21: “לא קרס” לבדו חלש. מעריכים בנפרד את “לא נשבר” ואת “אפשר לעקוב כשזה נשבר”.

7.5. מגבלות ונקודות לשים לב

Application Verifier נוח מאוד, אבל הוא לא קסם.

  • code path שלא עברו עליו בפועל לא נבדק
  • full page heap כבד
  • לפעמים stop יוצא גם מצד SDK של צד שלישי
  • ה-code path שעוברים בו שונה מאוד בין run עם fault injection לבין run בלי
  • זה לא כלי שעושה לבד את כל חקירת ה-managed heap leak

לכן החלוקה היא כזו:

  • trend לאורך זמן — לוגים פנימיים ו-counters
  • misuse בגבול native — Application Verifier
  • שחזור סיבתיות בזמן כשל — structured log + dump + debugger

חלוקת העבודה הזו הכי מתאימה בפועל.

חלוקת העבודה בחקירהtrend לאורך זמן נבדק בלוגים פנימיים וב-counters, misuse בגבול native נבדק ב-Application Verifier, ושחזור סיבתיות בזמן כשל נבדק ב-structured log, dump ו-debugger.trend לאורך זמןלוגים פנימיים ו-countersmisuse בגבול nativeApplication Verifierשחזור סיבתיות בזמן כשללוג, dump ו-debugger

איור 22: Application Verifier הוא לא כלי לכל דבר. מחלקים כלים לפי מה שרוצים לראות.

8. מתי להשתמש במה

  • חושדים ב-invalid handle או double close
    • Handles + !htrace
  • חושדים ב-heap corruption / use-after-free
    • Heaps + full page heap + !heap -p -a
  • רוצים לגרום למצב שמדמה מחסור בזיכרון או במשאבים
    • Low Resource Simulation
  • נשבר בהדרגה בהרצה ארוכה
    • קודם כול, Handle Count / Private Bytes / lifecycle log פנימיים
  • רוצים לבדוק DLL
    • מדליקים Application Verifier על ה-harness EXE שקורא לאותו DLL

אם פותחים הכול בבת אחת מההתחלה, בדרך כלל מקבלים לוגים בלתי קריאים. הרבה יותר ברור אם מתחילים מהבדיקה שקרובה ל-failure path שרוצים לראות.

9. סיכום

Application Verifier הוא runtime verifier לגבול ה-native / Win32 של Windows. עם Handles / Heaps / Locks / Memory / TLS / Low Resource Simulation אפשר לדרוך מוקדם על failure path שבדרך כלל כמעט לא מופיע.

מה שעזר בהקשר הזה: קל לעקוב עם !htrace כשיש בעיית handle, אפשר לגרום למצב שמדמה מחסור בזיכרון או במשאבים בלי לשבור את כל המכונה, ואפשר היה לבדוק אם הלוגים הפנימיים באמת מועילים באותו רגע.

בפועל מפרידים בין happy path + Basics לבין מסלול fault injection, מכינים harness EXE ומריצים תרחישים ב-process קצר-חיים. מעל זה משלבים לוגים פנימיים, dump ומידע debugger, ואת ה-trend עצמו של leak ארוך בודקים ב-counters פנימיים. זו חלוקת העבודה.

Application Verifier הוא כלי ש במקום לחכות במקרה לכשל נדיר, מביא אותו אליכם.

באפליקציית בקרת ציוד חשוב שלא יישבר, אבל חשוב באותה מידה להסביר מה קרה כשזה כן נשבר. במובן הזה, זה כלי שמתאים מאוד לעבודה בפועל.

חלק ראשון: חקירת קריסה בהרצה ארוכה של מצלמה תעשייתית — handle leak

10. מקורות

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

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

חקרי המקרה האלה מציגים גישה דומה לניתוח, לתעדוף או לעיצוב מחדש.

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

שאלות נפוצות

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

מה זה Application Verifier?
כלי runtime verification לאפליקציות user-mode ב-Windows. הוא מנטר בזמן ריצה איך האפליקציה משתמשת ב-OS API ואיך היא מנהלת משאבים, מזהה שימוש חשוד כמו invalid handle או heap corruption, ואפשר גם להזריק כשל בכוונה. בניגוד ל-static analysis או unit tests, זה כלי שמראה איך הקוד נשבר בפועל כשעוברים ב-code path הזה. לכן הוא מתאים לחשוף failure path שלא רואים בבדיקות פונקציונליות רגילות.
אפשר לשחזר מחסור בזיכרון עם Application Verifier?
עם Low Resource Simulation אפשר לגרום מוקדם לתופעה שמדמה מחסור בזיכרון או במשאבים, בלי באמת לרוקן את ה-RAM של המכונה. המנגנון הוא fault injection: קריאות API כמו HeapAlloc, VirtualAlloc, CreateFile ו-CreateEvent נכשלות בכוונה בהסתברות קבועה. אפשר גם לכוון את ה-injection ל-DLL ספציפי, וזה נוח כשיש wrapper פנימי מעורבב עם vendor SDK. אם פותחים הכול מההתחלה הלוגים הופכים לבלתי קריאים, אז מתחילים רק ממה שקרוב ל-failure path שרוצים לראות.
אפשר להשתמש ב-Application Verifier לחקירת handle leak?
כשמפעילים את בדיקת Handles אפשר לזהות שימוש ב-invalid handle, למשל שימוש חוזר ב-handle שכבר נסגר. בנוסף, handle tracing נדלק אוטומטית, ואפשר לראות ב-!htrace את ה-stack של open / close לאותו handle. אבל לא ריאלי להשאיר חקירת leak של EXE שרץ לאורך זמן רק ל-Application Verifier. בפועל משלבים דגימה תקופתית של Handle Count ולוגים פנימיים של resource lifecycle: את ה-trend מזהים בלוגים הפנימיים, ואת ה-misuse מזהים ב-verifier.
איך משתמשים ב-Application Verifier לבדיקת DLL?
היעד שמפעילים עליו את Application Verifier הוא ה-EXE של הבדיקה שמריץ בפועל את ה-DLL. אי אפשר להפעיל את זה אחרי שהתהליך כבר רץ; מגדירים ואז מפעילים את ה-EXE. ההגדרה גם נשארת עד שמוחקים אותה במפורש, לכן עדיף לכוון harness EXE ייעודי לבדיקות ולא את אפליקציית הייצור. תרחיש אחד לכל process מקל גם לראות diff של leak וגם להדליק ולכבות את ההגדרה.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג