לדלג לתוכן

9.2 הלינקר פתרון

פתרונות

פתרון 1

  1. קימפול ובדיקת סמלים:
gcc -c main.c -o main.o
gcc -c counter.c -o counter.o
gcc -c printer.c -o printer.o

nm main.o

                 U counter         # undefined - משתמש (extern)
                 U increment       # undefined - משתמש (הצהרה בלבד)
0000000000000000 T main            # defined - מגדיר
                 U print_counter   # undefined - משתמש

nm counter.o

0000000000000000 D counter         # defined in .data - משתנה גלובלי מאותחל
0000000000000000 T increment       # defined in .text - פונקציה

nm printer.o

                 U counter         # undefined - משתמש (extern)
0000000000000000 T print_counter   # defined in .text - פונקציה
                 U printf          # undefined - מ-libc

  1. חיבור והרצה:

gcc main.o counter.o printer.o -o program
./program

Counter = 0
Counter = 3

  1. בלי counter.o:

gcc main.o printer.o -o program

undefined reference to `counter'
undefined reference to `increment'

גם main.o וגם printer.o צריכים את counter, ו-main.o צריך את increment. שני הסמלים האלה מוגדרים רק ב-counter.o שהשמטנו.

  1. בלי printer.o:

gcc main.o counter.o -o program

undefined reference to `print_counter'

רק main.o צריך את print_counter, שמוגדר ב-printer.o שהשמטנו.


פתרון 2

  1. קימפול והרצה:

gcc -c strong.c -o strong.o
gcc -c weak.c -o weak.o
gcc strong.o weak.o -o program
./program

value = 42
value = 100

ב-strong.c המשתנה value מוגדר עם ערך התחלתי (int value = 42;) - זה סמל חזק. ב-weak.c המשתנה value לא מאותחל (int x;) - זה סמל חלש. הסמל החזק מנצח, אז הערך ההתחלתי הוא 42. שני הקבצים חולקים את אותו משתנה.

  1. אחרי שינוי ל-int value = 99;:

gcc strong.o weak.o -o program

multiple definition of `value'

עכשיו שני הקבצים מגדירים סמל חזק עם אותו שם - שגיאה.

  1. במקרה הראשון: סמל חזק + סמל חלש = הסמל החזק מנצח (בלי שגיאה). במקרה השני: שני סמלים חזקים = שגיאת multiple definition. הכלל: אסור שיהיו שני סמלים חזקים עם אותו שם.

  2. פתרון - להשתמש ב-static:

// strong.c
static int value = 42;  // מקומי - לא נגיש מבחוץ
// weak.c
static int value = 99;  // מקומי - לא מתנגש

עם static, כל קובץ מקבל עותק פרטי של value. הסמלים לא חשופים ללינקר ולכן אין התנגשות.


פתרון 3

  1. מימוש:
// mystring.c
#include "mystring.h"

int my_strlen(const char *s) {
    int len = 0;
    while (s[len] != '\0') {
        len++;
    }
    return len;
}

char *my_strcpy(char *dest, const char *src) {
    int i = 0;
    while (src[i] != '\0') {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
    return dest;
}

int my_strcmp(const char *s1, const char *s2) {
    while (*s1 && *s1 == *s2) {
        s1++;
        s2++;
    }
    return (unsigned char)*s1 - (unsigned char)*s2;
}

void my_strrev(char *s) {
    int len = my_strlen(s);
    for (int i = 0; i < len / 2; i++) {
        char temp = s[i];
        s[i] = s[len - 1 - i];
        s[len - 1 - i] = temp;
    }
}
  1. יצירת הספריה:
gcc -c mystring.c -o mystring.o
ar rcs libmystring.a mystring.o
  1. תוכנית ראשית:
// main.c
#include <stdio.h>
#include "mystring.h"

int main() {
    char buf[100];
    const char *hello = "Hello, World!";

    printf("strlen: %d\n", my_strlen(hello));

    my_strcpy(buf, hello);
    printf("strcpy: %s\n", buf);

    printf("strcmp(\"abc\", \"abd\"): %d\n", my_strcmp("abc", "abd"));
    printf("strcmp(\"abc\", \"abc\"): %d\n", my_strcmp("abc", "abc"));

    char rev[] = "abcdef";
    my_strrev(rev);
    printf("strrev: %s\n", rev);

    return 0;
}
  1. קימפול:

gcc main.c -L. -lmystring -o program
./program

strlen: 13
strcpy: Hello, World!
strcmp("abc", "abd"): -1
strcmp("abc", "abc"): 0
strrev: fedcba

  1. תוכן הספריה:

ar -t libmystring.a

mystring.o

  1. סמלים בספריה:

nm libmystring.a

mystring.o:
0000000000000000 T my_strcmp
0000000000000040 T my_strcpy
0000000000000070 T my_strlen
0000000000000090 T my_strrev

כל ארבע הפונקציות מוגדרות (T) בסקשן .text.


פתרון 4

  1. יצירת הספריות:
gcc -c liba.c -o liba.o
gcc -c libb.c -o libb.o
gcc -c main.c -o main.o
ar rcs liba.a liba.o
ar rcs libb.a libb.o
  1. ניסיון ראשון:
gcc main.o -L. -la -lb -o program

תלוי במקרה, עלול להצליח כי הלינקר עובר -la קודם, מוצא func_a (שצריך func_b), ואז עובר -lb ומוצא func_b (שצריך func_a). אבל func_a כבר נטען, אז זה עלול לעבוד. במקרים מסוימים ייכשל:

undefined reference to `func_a'
  1. ניסיון בסדר הפוך:
gcc main.o -L. -lb -la -o program

כאן main.o צריך func_a, אבל libb.a לא מכילה אותו. הלינקר ידלג על libb.a (כי func_b לא נדרש עדיין). אז כשיגיע ל-liba.a הוא ימצא func_a אבל func_a צריך func_b - ואין עוד ספריות:

undefined reference to `func_b'
  1. פתרון עם start-group:
gcc main.o -L. -Wl,--start-group -la -lb -Wl,--end-group -o program

או פתרון פשוט יותר - לחזור על הספריות:

gcc main.o -L. -la -lb -la -o program

עם --start-group/--end-group, הלינקר עובר על הספריות שביניהם שוב ושוב עד שכל הסמלים נפתרים. בחזרה הראשונה הוא לוקח func_a מ-liba ו-func_b מ-libb. בחזרה השנייה הוא מוודא שהכל פתור.


פתרון 5

  1. מהפלט של ld --verbose:

  2. נקודת הכניסה: ENTRY(_start) - הלינקר מגדיר _start כנקודת הכניסה, לא main.

  3. כתובת ההתחלה של סגמנט הקוד: בד"כ סביב 0x400000 (64-bit) או 0x08048000 (32-bit). המיקום המדויק תלוי בlinker script.
  4. סקשנים בסגמנט הקוד: .text, .rodata, .plt, .init, .fini ועוד - כל הסקשנים עם הרשאות קריאה והרצה.

  5. בדיקת כתובות:

gcc main.c -o program
readelf -h program | grep Entry

Entry point address: 0x401040

nm program | grep _start

0000000000401040 T _start

nm program | grep " main"

0000000000401136 T main

הכתובת של _start (0x401040) קודמת ל-main (0x401136). _start הוא הראשון שרץ, הוא מכין את הסביבה ואז קורא ל-main דרך __libc_start_main.

  1. בקובץ output.map נמצא משהו כזה:
.text           0x0000000000401000       0x1a5
 *(.text.unlikely .text.*_unlikely .text.unlikely.*)
 *(.text.exit .text.exit.*)
 *(.text.startup .text.startup.*)
 *(.text.hot .text.hot.*)
 *(.text .stub .text.*)
 .text          0x0000000000401000        0x25 main.o

.data           0x0000000000404000        0x10
 .data          0x0000000000404000         0x0 main.o

הסקשן .text הוצב בכתובת 0x401000, הסקשן .data ב-0x404000 (על דף זיכרון שונה, כי הרשאות שונות - הקוד הוא R-X והנתונים הם RW-).