לדלג לתוכן

דיבוג קרנל - פתרון

תרגיל 1 - עבודה עם dmesg

א. הפקודות אמורות לעבוד על כל מערכת לינוקס. נקודות חשובות:
- dmesg --level=err מסנן רק הודעות ברמת ERR
- dmesg -T מציג timestamps בפורמט קריא (תאריך ושעה) במקום שניות מאז ה-boot
- שימו לב שב-dmesg -T הזמנים עלולים להיות לא מדויקים אם המערכת הייתה ב-suspend

ב. טעינת מודול מציגה הודעות כמו:

[12345.678] dummy: module loaded

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

ג. דוגמאות להודעות נפוצות:
- 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:

sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
    @bytes = hist(args->ret);
}'

כש-Ctrl+C נלחץ, bpftrace מציג היסטוגרמה:

@bytes:
[1]                    12 |@@@@                        |
[2, 4)                 34 |@@@@@@@@@@@                 |
[4, 8)                 56 |@@@@@@@@@@@@@@@@@@          |
[8, 16)                89 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[16, 32)               45 |@@@@@@@@@@@@@@              |
...

ג. ספירת syscalls:

sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter {
    @[comm, args->id] = count();
}'

בפלט נראה לכל תהליך כמה פעמים כל 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 מנקה את הערך מהמפה כדי שלא ייצברו ערכים ישנים