9.2 הלינקר פתרון
פתרונות¶
פתרון 1¶
- קימפול ובדיקת סמלים:
U counter # undefined - משתמש (extern)
U increment # undefined - משתמש (הצהרה בלבד)
0000000000000000 T main # defined - מגדיר
U print_counter # undefined - משתמש
0000000000000000 D counter # defined in .data - משתנה גלובלי מאותחל
0000000000000000 T increment # defined in .text - פונקציה
U counter # undefined - משתמש (extern)
0000000000000000 T print_counter # defined in .text - פונקציה
U printf # undefined - מ-libc
- חיבור והרצה:
- בלי
counter.o:
גם main.o וגם printer.o צריכים את counter, ו-main.o צריך את increment. שני הסמלים האלה מוגדרים רק ב-counter.o שהשמטנו.
- בלי
printer.o:
רק main.o צריך את print_counter, שמוגדר ב-printer.o שהשמטנו.
פתרון 2¶
- קימפול והרצה:
ב-strong.c המשתנה value מוגדר עם ערך התחלתי (int value = 42;) - זה סמל חזק. ב-weak.c המשתנה value לא מאותחל (int x;) - זה סמל חלש. הסמל החזק מנצח, אז הערך ההתחלתי הוא 42. שני הקבצים חולקים את אותו משתנה.
- אחרי שינוי ל-
int value = 99;:
עכשיו שני הקבצים מגדירים סמל חזק עם אותו שם - שגיאה.
-
במקרה הראשון: סמל חזק + סמל חלש = הסמל החזק מנצח (בלי שגיאה). במקרה השני: שני סמלים חזקים = שגיאת
multiple definition. הכלל: אסור שיהיו שני סמלים חזקים עם אותו שם. -
פתרון - להשתמש ב-
static:
עם static, כל קובץ מקבל עותק פרטי של value. הסמלים לא חשופים ללינקר ולכן אין התנגשות.
פתרון 3¶
- מימוש:
// 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;
}
}
- יצירת הספריה:
- תוכנית ראשית:
// 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;
}
- קימפול:
- תוכן הספריה:
- סמלים בספריה:
mystring.o:
0000000000000000 T my_strcmp
0000000000000040 T my_strcpy
0000000000000070 T my_strlen
0000000000000090 T my_strrev
כל ארבע הפונקציות מוגדרות (T) בסקשן .text.
פתרון 4¶
- יצירת הספריות:
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
- ניסיון ראשון:
תלוי במקרה, עלול להצליח כי הלינקר עובר -la קודם, מוצא func_a (שצריך func_b), ואז עובר -lb ומוצא func_b (שצריך func_a). אבל func_a כבר נטען, אז זה עלול לעבוד. במקרים מסוימים ייכשל:
- ניסיון בסדר הפוך:
כאן main.o צריך func_a, אבל libb.a לא מכילה אותו. הלינקר ידלג על libb.a (כי func_b לא נדרש עדיין). אז כשיגיע ל-liba.a הוא ימצא func_a אבל func_a צריך func_b - ואין עוד ספריות:
- פתרון עם start-group:
או פתרון פשוט יותר - לחזור על הספריות:
עם --start-group/--end-group, הלינקר עובר על הספריות שביניהם שוב ושוב עד שכל הסמלים נפתרים. בחזרה הראשונה הוא לוקח func_a מ-liba ו-func_b מ-libb. בחזרה השנייה הוא מוודא שהכל פתור.
פתרון 5¶
-
מהפלט של
ld --verbose: -
נקודת הכניסה:
ENTRY(_start)- הלינקר מגדיר_startכנקודת הכניסה, לאmain. - כתובת ההתחלה של סגמנט הקוד: בד"כ סביב
0x400000(64-bit) או0x08048000(32-bit). המיקום המדויק תלוי בlinker script. -
סקשנים בסגמנט הקוד:
.text,.rodata,.plt,.init,.finiועוד - כל הסקשנים עם הרשאות קריאה והרצה. -
בדיקת כתובות:
הכתובת של _start (0x401040) קודמת ל-main (0x401136). _start הוא הראשון שרץ, הוא מכין את הסביבה ואז קורא ל-main דרך __libc_start_main.
- בקובץ
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-).