לדלג לתוכן

9.3 סקריפטים של הלינקר תרגול

תרגול - סקריפטים של הלינקר

תרגיל 1 - הlinker script של ברירת המחדל

  1. הריצו ld --verbose והפנו את הפלט לקובץ: ld --verbose > default_ld.txt
  2. פתחו את הקובץ ומצאו:
  3. מהו ה-ENTRY? (מה נקודת הכניסה של ברירת המחדל?)
  4. מצאו את הסקשן .text - אילו סקשנים נוספים נכנסים לתוכו מלבד *(.text)?
  5. מצאו את הסקשן .data - מה בא לפניו? מה אחריו?
  6. חפשו את הביטוי ALIGN - כמה פעמים הוא מופיע? על אילו ערכים מיישרים?
  7. כתבו תוכנית hello world פשוטה, קמפלו אותה, ובדקו:
    readelf -l program
    

    כמה סגמנטים מסוג LOAD יש? מה ההרשאות של כל אחד?

תרגיל 2 - linker script בסיסי

כתבו linker script בשם basic.ld שמגדיר:
- נקודת כניסה: main
- כתובת התחלה: 0x400000
- סקשנים: .text, .rodata, .data, .bss - בסדר הזה

צרו את הקובץ הבא:

// simple.c
#include <stdio.h>

const char message[] = "Hello from custom linker script!";
int counter = 0;

int main() {
    counter++;
    printf("%s (counter=%d)\n", message, counter);
    return 0;
}
  1. קמפלו עם הlinker script שלכם: gcc -T basic.ld simple.c -o simple
  2. האם זה עובד? אם לא, למה? (רמז: חשבו מה קורה עם libc ועם _start)
  3. נסו: gcc -T basic.ld -nostdlib simple.c -o simple - מה השגיאה?
  4. הסבירו למה linker script מותאם עם libc דורש זהירות מיוחדת.

תרגיל 3 - תוכנית freestanding עם linker script

צרו תוכנית שרצה בלי libc. הlinker script:

/* free.ld */
ENTRY(_start)

SECTIONS {
    . = 0x400000;

    .text : {
        *(.text)
        *(.text.*)
    }

    .rodata : {
        *(.rodata)
        *(.rodata.*)
    }

    . = ALIGN(4096);

    .data : {
        *(.data)
    }

    .bss : {
        *(.bss)
        *(COMMON)
    }
}

הקוד:

// free.c
static void my_exit(int code) __attribute__((noreturn));
static void my_exit(int code) {
    __asm__ volatile("syscall" : : "a"(60), "D"(code));
    __builtin_unreachable();
}

static long my_write(int fd, const void *buf, long len) {
    long ret;
    __asm__ volatile("syscall" : "=a"(ret) : "a"(1), "D"(fd), "S"(buf), "d"(len) : "rcx", "r11", "memory");
    return ret;
}

void _start(void) {
    my_write(1, "It works!\n", 10);
    my_exit(0);
}
  1. קמפלו: gcc -nostdlib -static -T free.ld free.c -o free_program
  2. הריצו ובדקו שעובד.
  3. בדקו את גודל הקובץ עם ls -la ו-size free_program. השוו לתוכנית hello world סטטית רגילה.
  4. הריצו readelf -S free_program - כמה סקשנים יש? השוו לתוכנית רגילה.
  5. הריצו readelf -l free_program - כמה סגמנטים מסוג LOAD? מה ההרשאות?

תרגיל 4 - הגדרת סמלים בlinker script

שנו את הlinker script מתרגיל 3 כך שיגדיר סמלים:

ENTRY(_start)

SECTIONS {
    . = 0x400000;

    .text : {
        _text_start = .;
        *(.text)
        *(.text.*)
        _text_end = .;
    }

    .rodata : {
        *(.rodata)
        *(.rodata.*)
    }

    _text_size = _text_end - _text_start;

    . = ALIGN(4096);

    .data : {
        _data_start = .;
        *(.data)
        _data_end = .;
    }

    .bss : {
        _bss_start = .;
        *(.bss)
        *(COMMON)
        _bss_end = .;
    }
}

שנו את הקוד כדי להדפיס את הכתובות (תצטרכו לכתוב פונקציה שממירה מספר למחרוזת בלי libc):

  1. כתבו פונקציה void print_hex(unsigned long value) שמדפיסה מספר בבסיס 16 באמצעות my_write.
  2. השתמשו ב-extern char _text_start; וכו' כדי לגשת לסמלים שהגדרתם.
  3. הדפיסו את כתובת ההתחלה והסיום של כל סקשן.
  4. חשבו את גודל סקשן .text מתוך הסמלים, וודאו שהוא תואם את מה שreadelf מראה.

תרגיל 5 - סקשנים מותאמים אישית

צרו תוכנית freestanding עם סקשן מותאם אישית:

  1. הגדירו סקשן בשם .startup שיכיל את הקוד של _start.
  2. הגדירו סקשן בשם .handlers שיכיל פונקציות טיפול.
  3. כתבו linker script ששם את .startup ראשון (בכתובת 0x400000), ואחריו .text, ואחריו .handlers.
  4. השתמשו ב-__attribute__((section(".startup"))) ו-__attribute__((section(".handlers"))) כדי לשים פונקציות בסקשנים הנכונים.
  5. הגדירו סמלים בlinker script שמסמנים את תחילת וסוף כל סקשן.
  6. וודאו עם readelf שהסקשנים מסודרים כצפוי.