לדלג לתוכן

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

פתרונות

פתרון 1

  1. הפניית הפלט:
ld --verbose > default_ld.txt
  1. מציאת האלמנטים:

  2. ה-ENTRY הוא ENTRY(_start) - נקודת הכניסה של ברירת המחדל היא _start.

  3. הסקשן .text מכיל הרבה יותר מ-*(.text):

    .text : {
        *(.text.unlikely .text.*_unlikely .text.unlikely.*)
        *(.text.exit .text.exit.*)
        *(.text.startup .text.startup.*)
        *(.text.hot .text.hot.*)
        *(.text .stub .text.*)
    }
    

    סקשנים כמו .text.hot ו-.text.unlikely משמשים את הקומפיילר לPGO (Profile-Guided Optimization) - קוד "חם" ממוקם ביחד לביצועי cache טובים יותר.

  4. הסקשן .data בא אחרי סקשנים כמו .eh_frame ו-.ctors (constructors). אחריו בא .bss.

  5. הביטוי ALIGN מופיע עשרות פעמים. ערכי יישור נפוצים: ALIGN(4096) (גבול דף), ALIGN(8), ALIGN(4), ALIGN(64) (גבול cache line).

  6. בדיקת סגמנטים:

gcc hello.c -o hello
readelf -l hello

בד"כ יש 2-3 סגמנטים מסוג LOAD:
- הראשון: R-X (קוד + rodata) - קריאה והרצה
- השני: RW- (data + bss) - קריאה וכתיבה
- לפעמים שלישי: R-- (rodata בנפרד)


פתרון 2

  1. הlinker script:
/* basic.ld */
ENTRY(main)

SECTIONS {
    . = 0x400000;

    .text : { *(.text) *(.text.*) }
    .rodata : { *(.rodata) *(.rodata.*) }
    .data : { *(.data) *(.data.*) }
    .bss : { *(.bss) *(.bss.*) *(COMMON) }
}
  1. הפקודה gcc -T basic.ld simple.c -o simple עלולה לעבוד בחלקה אבל להיתקל בבעיות. הבעיה: כשמשתמשים ב-libc, ה-linker script שלנו צריך לטפל גם בסקשנים של libc (כמו .init, .fini, .plt, .got ועוד) שאנחנו לא הגדרנו. gcc משתמש בlinker script של ברירת המחדל שכולל את כל הסקשנים האלה.

  2. עם -nostdlib:

gcc -T basic.ld -nostdlib simple.c -o simple

נקבל שגיאות undefined reference ל-printf כי ביטלנו את libc. ובנוסף, ה-ENTRY שלנו הוא main אבל בלי _start הקרנל לא ידע איך לקרוא ל-main.

  1. שימוש ב-linker script מותאם ביחד עם libc דורש זהירות כי libc מצפה לlinker script מלא שכולל הגדרות של סקשנים רבים (.plt, .got, .dynamic, .init_array, .fini_array ועוד). אם חסר סקשן, libc לא תעבוד כראוי. בפועל, linker scripts מותאמים משמשים בעיקר לקוד freestanding (בלי libc).

פתרון 3

  1. קימפול:
gcc -nostdlib -static -T free.ld free.c -o free_program
  1. הרצה:
./free_program
# It works!
  1. השוואת גודל:
ls -la free_program
# בסביבות 4-8 KB

gcc -static -o hello_static hello.c
ls -la hello_static
# בסביבות 800-900 KB

size free_program
# text    data    bss    dec    hex
#   90       0      0     90     5a

size hello_static
# text    data    bss     dec    hex
# 774321  23280  23016  820617  c8589

התוכנית שלנו קטנה פי 100 לפחות - כי אין libc, אין קוד אתחול, אין שום דבר מיותר.

  1. סקשנים:
readelf -S free_program

לתוכנית שלנו יש מעט סקשנים (5-6) לעומת תוכנית רגילה שיש לה 30+.

  1. סגמנטים:
readelf -l free_program

סגמנט LOAD אחד או שניים (תלוי אם יש נתונים), לעומת 10+ בתוכנית רגילה.


פתרון 4

הקוד עם הדפסת כתובות בהקסדצימלי:

// free_symbols.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;
}

static void print_str(const char *s) {
    int len = 0;
    while (s[len]) len++;
    my_write(1, s, len);
}

static void print_hex(unsigned long value) {
    char buf[18]; // "0x" + 16 hex digits
    buf[0] = '0';
    buf[1] = 'x';
    const char *hex = "0123456789abcdef";
    for (int i = 15; i >= 0; i--) {
        buf[2 + i] = hex[value & 0xf];
        value >>= 4;
    }
    my_write(1, buf, 18);
}

extern char _text_start;
extern char _text_end;
extern char _data_start;
extern char _data_end;
extern char _bss_start;
extern char _bss_end;

void _start(void) {
    print_str("Text:  ");
    print_hex((unsigned long)&_text_start);
    print_str(" - ");
    print_hex((unsigned long)&_text_end);
    print_str("\n");

    print_str("Data:  ");
    print_hex((unsigned long)&_data_start);
    print_str(" - ");
    print_hex((unsigned long)&_data_end);
    print_str("\n");

    print_str("BSS:   ");
    print_hex((unsigned long)&_bss_start);
    print_str(" - ");
    print_hex((unsigned long)&_bss_end);
    print_str("\n");

    unsigned long text_size = (unsigned long)&_text_end - (unsigned long)&_text_start;
    print_str("Text size: ");
    print_hex(text_size);
    print_str("\n");

    my_exit(0);
}

קימפול:

gcc -nostdlib -static -T symbols.ld free_symbols.c -o sym_program
./sym_program

פלט לדוגמה:

Text:  0x0000000000400000 - 0x000000000040012a
Data:  0x0000000000401000 - 0x0000000000401000
BSS:   0x0000000000401000 - 0x0000000000401000
Text size: 0x000000000000012a

אימות עם readelf:

readelf -S sym_program | grep .text

הגודל שreadelf מראה צריך להתאים ל-text_size שהדפסנו.


פתרון 5

הlinker script:

/* custom_sections.ld */
ENTRY(_start)

SECTIONS {
    . = 0x400000;

    .startup : {
        _startup_start = .;
        *(.startup)
        _startup_end = .;
    }

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

    .handlers : {
        _handlers_start = .;
        *(.handlers)
        _handlers_end = .;
    }

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

    . = ALIGN(4096);

    .data : {
        *(.data)
    }

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

הקוד:

// custom.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;
}

__attribute__((section(".handlers")))
void handler_a(void) {
    my_write(1, "Handler A\n", 10);
}

__attribute__((section(".handlers")))
void handler_b(void) {
    my_write(1, "Handler B\n", 10);
}

void do_work(void) {
    my_write(1, "Doing work...\n", 14);
}

__attribute__((section(".startup")))
void _start(void) {
    handler_a();
    do_work();
    handler_b();
    my_exit(0);
}

קימפול ובדיקה:

gcc -nostdlib -static -T custom_sections.ld custom.c -o custom
./custom
# Handler A
# Doing work...
# Handler B

readelf -S custom

בפלט של readelf נראה שהסקשנים מסודרים בסדר: .startup, .text, .handlers - בדיוק כפי שהגדרנו בlinker script. כתובות ההתחלה של כל סקשן יתאימו לסמלים שהגדרנו.