דיבוג קרנל - פתרון¶
תרגיל 1 - עבודה עם dmesg¶
א. הפקודות אמורות לעבוד על כל מערכת לינוקס. נקודות חשובות:
- dmesg --level=err מסנן רק הודעות ברמת ERR
- dmesg -T מציג timestamps בפורמט קריא (תאריך ושעה) במקום שניות מאז ה-boot
- שימו לב שב-dmesg -T הזמנים עלולים להיות לא מדויקים אם המערכת הייתה ב-suspend
ב. טעינת מודול מציגה הודעות כמו:
או הודעות ספציפיות למודול. אם המודול לא נמצא, תראו הודעת שגיאה.
ג. דוגמאות להודעות נפוצות:
- ACPI Error: ... - בעיה בתצורת ACPI של ה-BIOS
- Out of memory: Killed process ... - OOM killer הרג תהליך
- TCP: ... possible SYN flooding on port ... - חשד להתקפת SYN flood
- EXT4-fs warning: ... - אזהרה ממערכת הקבצים
תרגיל 2 - קריאת kernel oops¶
א. השגיאה קרתה בפונקציה linked_list_search במודול my_module, ב-offset 0x1e מתחילת הפונקציה.
ב. הכתובת שגרמה ל-page fault: 0x0000000000000010. זו כתובת קרובה מאוד ל-0 (NULL), מה שמרמז על NULL pointer dereference עם offset. כנראה ניסינו לגשת לשדה (field) שנמצא ב-offset 0x10 מתוך struct שהמצביע אליו הוא NULL.
ג. RAX = 0x0000000000000000 (NULL). זה המצביע שגרם לבעיה. הקוד כנראה ניסה לקרוא מ-RAX + 0x10, שזה 0 + 0x10 = 0x10 - בדיוק הכתובת של ה-page fault.
ד. ה-Call trace מראה:
linked_list_search <-- כאן קרתה השגיאה
device_ioctl <-- הפונקציה שקראה ל-search
__x64_sys_ioctl <-- טיפול ב-ioctl syscall
do_syscall_64 <-- מנגנון syscall
entry_SYSCALL <-- כניסה מ-user space
הזרימה: תוכנית user space (PID 3456, "test_program") קראה ל-ioctl, שגרם ל-device_ioctl לקרוא ל-linked_list_search, ששם קרתה השגיאה.
ה. שחזור הקוד הבעייתי:
הפונקציה linked_list_search כנראה סורקת רשימה מקושרת ומחפשת ערך (RSI = RCX = 0x42 = הערך שמחפשים). הקוד כנראה נראה כך:
struct node *linked_list_search(struct list_head *head, int key)
{
struct node *current = /* first element */; // RAX
while (current != NULL) { // בדיקת NULL
if (current->key == key) // גישה ל-current + 0x10
return current;
current = current->next;
}
return NULL;
}
הבעיה: current (RAX) הוא NULL, אבל הקוד ניסה לגשת ל-current->key (ב-offset 0x10 מתוך ה-struct). זה אומר שאלמנט ברשימה המקושרת הוא NULL - אולי הרשימה פגומה, או שהמצביע ל-next לא אותחל נכון.
תרגיל 3 - Ftrace¶
א. מעקב אחרי open syscalls:
ls /tmp פותח מספר קבצים:
- את הספריה /tmp עצמה (כדי לקרוא את תוכנה)
- ספריות locale
- ספריות shared libraries (libc, ld-linux, וכו')
מספר הקריאות משתנה, אבל בדרך כלל 5-15 קריאות ל-open/openat.
ב. function_graph tracer:
הפלט מראה את כל הפונקציות שנקראות בתוך do_sys_openat2:
0) | do_sys_openat2() {
0) | getname() {
0) 0.456 us | kmem_cache_alloc();
0) 1.234 us | }
0) | do_filp_open() {
0) | path_openat() {
0) | alloc_empty_file() {
0) 0.321 us | kmem_cache_alloc();
0) 0.789 us | }
0) | link_path_walk() {
...
0) + 12.345 us | }
זמן טיפוסי לפתיחת קובץ: 10-50 מיקרושניות, תלוי אם הנתונים ב-cache.
תרגיל 4 - perf¶
א. פרופיילינג:
עבור stress --cpu 1, הפונקציה שצורכת הכי הרבה CPU היא בדרך כלל הלולאה הפנימית של stress (חישובים מתמטיים). ב-perf report תראו אחוזים לכל פונקציה.
עבור dd if=/dev/urandom:
- _extract_crng או chacha20_block - יצירת מספרים אקראיים
- copy_user_enhanced_fast_string - העתקה מ-kernel ל-user space
- פונקציות VFS
ב. אירועי חומרה:
פלט דוגמה:
Performance counter stats for 'ls -la /usr/bin/':
12,345,678 cycles
9,876,543 instructions # 0.80 insn per cycle
234,567 cache-references
12,345 cache-misses # 5.26% of all cache refs
1,234,567 branch-instructions
23,456 branch-misses # 1.90% of all branches
- IPC (Instructions Per Cycle): 0.80 - פחות מ-1 אומר שה-CPU ממתין הרבה (לזיכרון, ל-branch resolution). IPC גבוה (2-4) אומר שה-CPU מנצל היטב את ה-pipeline.
- Cache miss rate: 5.26% - סביר. אם מעל 10-20%, יש בעיית locality.
- Branch miss rate: 1.90% - סביר. מעל 5% מרמז על קוד עם הרבה branches לא צפויים.
ג. perf trace של curl:
syscalls נפוצים:
- read/write/sendto/recvfrom - תקשורת רשת
- poll/epoll_wait - המתנה לנתונים
- mmap/mprotect - טעינת ספריות
- openat/close - פתיחת קבצי תצורה, certificates
רוב הזמן מבולה ב-poll/recvfrom (המתנה לתגובה מהשרת).
תרגיל 5 - bpftrace¶
א. מעקב אחרי תהליכים חדשים:
sudo bpftrace -e 'tracepoint:sched:sched_process_exec {
printf("PID=%d COMM=%s FILE=%s\n", pid, comm, str(args->filename));
}'
כל פעם שתהליך מבצע exec(), נראה את ה-PID, שם התהליך, ושם הקובץ שמורץ.
ב. היסטוגרמה של גדלי read:
כש-Ctrl+C נלחץ, bpftrace מציג היסטוגרמה:
@bytes:
[1] 12 |@@@@ |
[2, 4) 34 |@@@@@@@@@@@ |
[4, 8) 56 |@@@@@@@@@@@@@@@@@@ |
[8, 16) 89 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[16, 32) 45 |@@@@@@@@@@@@@@ |
...
ג. ספירת syscalls:
בפלט נראה לכל תהליך כמה פעמים כל syscall נקרא. שמות ה-syscalls מופיעים כמספרים - אפשר לתרגם עם /usr/include/asm/unistd_64.h.
ד. (בונוס) latency של write:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_write {
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_write /@start[tid]/ {
@latency_us[comm] = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
הסבר:
- ב-sys_enter_write שומרים את הזמן הנוכחי (nsecs) במפה לפי thread ID
- ב-sys_exit_write מחשבים את ההפרש (זמן הפעולה)
- ממירים לננו-שניות ליחידות מיקרו-שניות (חלוקה ב-1000)
- מקבצים לפי שם התהליך (comm) ומציגים כהיסטוגרמה
- delete מנקה את הערך מהמפה כדי שלא ייצברו ערכים ישנים