9.3 סקריפטים של הלינקר פתרון
פתרונות¶
פתרון 1¶
- הפניית הפלט:
-
מציאת האלמנטים:
-
ה-ENTRY הוא
ENTRY(_start)- נקודת הכניסה של ברירת המחדל היא_start. -
הסקשן
.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 טובים יותר. -
הסקשן
.dataבא אחרי סקשנים כמו.eh_frameו-.ctors(constructors). אחריו בא.bss. -
הביטוי
ALIGNמופיע עשרות פעמים. ערכי יישור נפוצים:ALIGN(4096)(גבול דף),ALIGN(8),ALIGN(4),ALIGN(64)(גבול cache line). -
בדיקת סגמנטים:
בד"כ יש 2-3 סגמנטים מסוג LOAD:
- הראשון: R-X (קוד + rodata) - קריאה והרצה
- השני: RW- (data + bss) - קריאה וכתיבה
- לפעמים שלישי: R-- (rodata בנפרד)
פתרון 2¶
- הlinker script:
/* basic.ld */
ENTRY(main)
SECTIONS {
. = 0x400000;
.text : { *(.text) *(.text.*) }
.rodata : { *(.rodata) *(.rodata.*) }
.data : { *(.data) *(.data.*) }
.bss : { *(.bss) *(.bss.*) *(COMMON) }
}
-
הפקודה
gcc -T basic.ld simple.c -o simpleעלולה לעבוד בחלקה אבל להיתקל בבעיות. הבעיה: כשמשתמשים ב-libc, ה-linker script שלנו צריך לטפל גם בסקשנים של libc (כמו.init,.fini,.plt,.gotועוד) שאנחנו לא הגדרנו. gcc משתמש בlinker script של ברירת המחדל שכולל את כל הסקשנים האלה. -
עם
-nostdlib:
נקבל שגיאות undefined reference ל-printf כי ביטלנו את libc. ובנוסף, ה-ENTRY שלנו הוא main אבל בלי _start הקרנל לא ידע איך לקרוא ל-main.
- שימוש ב-linker script מותאם ביחד עם libc דורש זהירות כי libc מצפה לlinker script מלא שכולל הגדרות של סקשנים רבים (
.plt,.got,.dynamic,.init_array,.fini_arrayועוד). אם חסר סקשן, libc לא תעבוד כראוי. בפועל, linker scripts מותאמים משמשים בעיקר לקוד freestanding (בלי libc).
פתרון 3¶
- קימפול:
- הרצה:
- השוואת גודל:
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, אין קוד אתחול, אין שום דבר מיותר.
- סקשנים:
לתוכנית שלנו יש מעט סקשנים (5-6) לעומת תוכנית רגילה שיש לה 30+.
- סגמנטים:
סגמנט 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);
}
קימפול:
פלט לדוגמה:
Text: 0x0000000000400000 - 0x000000000040012a
Data: 0x0000000000401000 - 0x0000000000401000
BSS: 0x0000000000401000 - 0x0000000000401000
Text size: 0x000000000000012a
אימות עם readelf:
הגודל ש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. כתובות ההתחלה של כל סקשן יתאימו לסמלים שהגדרנו.