מנהל הזיכרון מבפנים¶
נניח ש־VirtualQuery מציגה אזור committed, אבל !pte אינו מציג דף פיזי תושב כפי שציפינו. שתי התצפיות יכולות להיות נכונות. האחת מתארת התחייבות וטווח במרחב התהליך, והשנייה מתארת מצב תרגום ברגע מסוים.
כדי לחקור את מנהל הזיכרון צריך להפריד בין שלוש מפות: תיאור טווחים וירטואליים, טבלאות תרגום ומידע על מסגרות פיזיות. אלה מפות קשורות, אך כל אחת עונה על שאלה אחרת.
נותנים לתוכנית לעצור בנקודות ידועות¶
הניסוי משתמש בתוכנית משיעור 5.1 ובסמלים שלה. בונים אותה עם שם התוצר virtual_memory.exe, עם אופטימיזציה כבויה ועם PDB. הפונקציה show נקראת אחרי שינויים מוגדרים, ולכן היא נקודת עצירה נוחה.
ב־WinDbg של user mode פותחים את התוכנית ומגדירים breakpoint ב־show. אם שם הסמל המעוטר שונה, משתמשים תחילה בחיפוש x כדי לראות את השמות הזמינים.
בכל עצירה רושמים את label ואת הכתובת שהפונקציה קיבלה. מבצעים !address על הכתובת שנצפתה כדי לקשר אותה לאזור המתאים. אין להעתיק כתובת מהדוגמה של מישהו אחר; ASLR והקצאות הופכים אותה לבלתי רלוונטית להרצה שלנו.
ה־VAD מתאר שטח במפת התהליך¶
המבנה VAD, כלומר virtual address descriptor, מתאר טווחים וירטואליים ומאפיינים הקשורים אליהם. הוא מסייע למנהל הזיכרון לדעת אילו אזורים שייכים למיפוי או להקצאה ואילו פעולות מותרות עליהם.
ה־VAD אינו טבלה של כל byte, ואינו רשימה של frames תושבים. טווח גדול יכול להיות מיוצג בתיאור אחד גם כאשר רק חלק מהדפים נגעו בפועל. שינויי הגנה או מיפוי יכולים להשפיע על ארגון התיאורים.
בגרסאות שונות אפשר לפגוש שדות וסוגים פנימיים שונים. אין לבנות תוכנית שקוראת offset ידוע מתוך EPROCESS ומטיילת בעץ ללא חוזה. במהלך חקירה משתמשים ב־symbols תואמים ובפקודת !vad ביעד שבו היא נתמכת.
ה־PTE מחבר מצב תרגום לדף¶
רשומת page table מתארת תרגום או מצב שמערכת ההפעלה צריכה לפרש. כשהדף תושב ותקף, אפשר לקשר ל־PFN ולהרשאות חומרה. כשהוא אינו במצב הזה, הביטים יכולים לייצג מידע תוכנה כמו מעבר, demand zero או backing מתאים לפי המימוש.
אין לקרוא כל PTE כאילו הוא תמיד כתובת פיזית תקפה. הביטים שמציינים סוג ומצב קובעים איך מפרשים את שאר המידע. שימוש ב־PFN שהוסק מרשומה לא תקפה יכול להוביל למסקנה שגויה לחלוטין.
גם הרשאות הגישה נובעות ממסלול תרגום ומדיניות, ולא רק מביט בודד שמתבוננים בו בלי הקשר. פרטי KVA, בידוד ומנגנוני וירטואליזציה יכולים להשפיע על מה שרואים ביעד מסוים.
מסגרת פיזית יכולה לשרת כמה מפות¶
ה־PFN database מנהל מידע על frames פיזיים, כולל קשרים וספירות שמשמשים את מנהל הזיכרון. דף פיזי יכול להיות משותף בין מיפויים, יכול לעבור בין מצבים ויכול להישאר בזיכרון גם כשאינו תושב כרגע ב־working set של process מסוים.
הפקודה !pfn מקבלת מספר frame, לא כתובת וירטואלית. את המספר מפיקים מתרגום תקף מתאים, ורק אז חוקרים את המצב. אלה יחידות שונות גם אם שתיהן מודפסות בהקסדצימלי.
אין להניח שספירת references פנימית היא מספר ה־pointers של C++ שמחזיקים כתובת. מצביעים גולמיים של היישום אינם נרשמים כל אחד במנהל הזיכרון. הספירות שייכות למבני המערכת ולחוזים שלה.
חקירת kernel מתבצעת ביעד מתאים¶
החלק הפנימי של המעבדה דורש VM או יעד kernel debugging שהוגדר מראש. במחשב רגיל עם user debugger אפשר לחקור VirtualQuery, !address ומידע תושבות, אך אין לטעון שהדבר נותן גישה מלאה ל־VAD ול־PFN.
ב־kernel debugger מוצאים את process המעבדה באמצעות !process, בוחרים את ההקשר המתאים ומעתיקים מהפלט את שורש ה־VAD ואת כתובת הניסוי. לאחר מכן מפעילים !vad, !pte ו־!pfn לפי היחידה שכל פקודה מצפה לה.
הפקודות הבאות מציגות עזרה ומידע על הטיפוסים במערכת היעד, בלי לקבע כתובות או offsets.
הזמינות והפרטים תלויים ב־symbols וב־build. אם הרחבה אינה מקבלת אפשרות עזרה מסוימת, פותחים את התיעוד המתאים ולא מנחשים כתובת. את הפקודות עם כתובת אמיתית בונים מהתוצאה שנצפתה ביעד.
משווים שינוי אחד בכל פעם¶
בשלב reserve מצפים לתיאור של טווח שנשמר, אך לא לכל דף תושב. אחרי commit מתווספת התחייבות. אחרי נגיעה משתנה מסלול הדף הפיזי. אחרי decommit נעלמת זכות השימוש בתוכן, ואחרי release הטווח יכול לחזור לשימוש אחר.
הדוח משייך כל שינוי למפה המתאימה. אם שני builds מציגים מבנה שונה אך מקיימים אותו חוזה API, זו תוצאה חשובה: היא מראה למה אפליקציה נתמכת נשענת על החוזה ולא על layout פנימי.
לקריאה נוספת: חקירת VAD, חקירת PTE, חקירת PFN ומפת כתובות ב־WinDbg.