تعرفنا في المقال السابق على الملفات القابلة للتنفيذ في نظام التشغيل وتمثيلها باستخدام الصيغة ELF وسنوضح في هذا المقال بعض المفاهيم المتعلقة بصيغة ملفات ELF مثل تنقيح الأخطاء Debugging وكيفية إنشاء أقسام مخصصة فيها وسكربتات الرابط Linker Scripts التي يستخدمها الرابط لبناء الأقسام Sections المُكوِّنة للمقاطع Segments، ولكن لنتعرّف أولًا على مفهوم ملفات ELF القابلة للتنفيذ.
ملفات ELF القابلة للتنفيذ
تُعَد الملفات القابلة للتنفيذ أحد الاستخدامات الأساسية لصيغة ELF. يحتوي الملف الثنائي على كل ما هو مطلوب لنظام التشغيل لتنفيذ الشيفرة البرمجية بالطريقة المطلوبة، حيث صُمِّم الملف التنفيذي لتشغيله في عملية ذات فضاء عناوين فريد، لذا يمكن للشيفرة البرمجية وضع افتراضات حول مكان تحميل أجزاء البرنامج المختلفة في الذاكرة.
يوضح المثال الآتي اختبار أجزاء ملفٍ قابل للتنفيذ باستخدام أداة readelf
. يمكننا أن نرى العناوين الوهمية التي يجب وضع مقاطع LOAD
فيها، حيث يمكننا أن نرى أنّ أحد هذه المقاطع مخصصٌ للشيفرة البرمجية ويمتلك أذونات القراءة والتنفيذ فقط، وهناك مقطع آخر مخصصٌ للبيانات ولديه أذونات القراءة والكتابة دون وجود أذونات التنفيذ، فبدونها لن تُميَّز الصفحات التي تدعم خطأ ما بأن لها أذونات التنفيذ حتى إن سمح هذا الخطأ للمهاجم بإدخال بيانات عشوائية، وبالتالي لن تسمح معظم المعالجات بأيّ تنفيذ للشيفرة البرمجية في تلك الصفحات.
$ readelf --segments /bin/ls Elf file type is EXEC (Executable file) Entry point 0x4046d4 There are 8 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001c0 0x00000000000001c0 R E 8 INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000019ef4 0x0000000000019ef4 R E 200000 LOAD 0x000000000001a000 0x000000000061a000 0x000000000061a000 0x000000000000077c 0x0000000000001500 RW 200000 DYNAMIC 0x000000000001a028 0x000000000061a028 0x000000000061a028 0x00000000000001d0 0x00000000000001d0 RW 8 NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c 0x0000000000000044 0x0000000000000044 R 4 GNU_EH_FRAME 0x0000000000017768 0x0000000000417768 0x0000000000417768 0x00000000000006fc 0x00000000000006fc R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 8 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07
يجب تحميل مقاطع البرنامج على هذه العناوين، حيث تتمثل الخطوة الأخيرة للرابط Linker في تحليل معظم المنقولات Relocations وتصحيحها باستخدام العناوين المطلقة المُفترَضة، ثم تجاهل البيانات التي تصف الانتقال في الملف الثنائي النهائي دون وجود طريقة لإيجاد هذه المعلومات بعد الآن.
تحتوي الملفات القابلة للتنفيذ عمومًا على اعتماديات Dependencies خارجية للمكتبات المشتركة Shared Libraries أو أجزاء من الشيفرة البرمجية المشتركة يمكن تجريدها ومشاركتها بين أجزاء النظام بأكمله، حيث تتعلق جميع الأجزاء الغريبة في المثال السابق باستخدام المكتبات المشتركة التي سنوضّحها لاحقًا.
تنقيح الأخطاء Debugging
يُشار تقليديًا إلى الطريقة الأساسية لتنقيح أخطاء ما بعد التعطل باسم التفريغ الأساسي Core Dump، حيث يأتي مصطلح الأساسي Core من الخصائص الفيزيائية الأصلية للذاكرة المغناطيسية الأساسية التي تستخدم اتجاه الحلقات المغناطيسية الصغيرة لتخزين الحالة. يُعَد التفريغ الأساسي لقطة كاملة للبرنامج عند عمله في وقت معين، ويمكن بعد ذلك استخدام منقح أخطاء Debugger لفحص هذا التفريغ وإعادة بناء حالة البرنامج. يوضح المثال التالي نموذجًا لبرنامج يكتب في موقع ذاكرة عشوائية لغرض التعطل، حيث ستتوقف العمليات ويُسجَّل تفريغ للحالة الحالية:
$ cat coredump.c int main(void) { char *foo = (char*)0x12345; *foo = 'a'; return 0; } $ gcc -Wall -g -o coredump coredump.c $ ./coredump Segmentation fault (core dumped) $ file ./core ./core: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-style, from './coredump' $ gdb ./coredump ... (gdb) core core [New LWP 31614] Core was generated by `./coredump'. Program terminated with signal 11, Segmentation fault. #0 0x080483c4 in main () at coredump.c:3 3 *foo = 'a'; (gdb)
وبالتالي فإن ملف التفريغ الأساسي هو مجرد ملف ELF يحتوي على مجموعة من الأقسام التي يفهمها منقح الأخطاء لتمثيل أجزاء من البرنامج المُشغَّل.
الرموز ومعلومات تنقيح الأخطاء
يتطلب منقح الأخطاء gdb الملف القابل للتنفيذ الأصلي وملف التفريغ الأساسي لإعادة بناء بيئة جلسة تنقيح الأخطاء. لاحظ أن الملف القابل للتنفيذ الأصلي أُنشئ باستخدام الراية -g
التي توجه المصرِّف Compiler لتضمين جميع معلومات الأخطاء، حيث يجري الاحتفاظ بالمعلومات المتعلقة بعملية تنقيح الأخطاء الإضافية في أقسام خاصة من ملف ELF، إذ تصف هذه المعلومات بالتفصيل أشياءً مثل قيم المسجّل التي تحتوي حاليًا على المتغيرات المستخدمة في الشيفرة البرمجية وحجم المتغيرات وطول المصفوفات وغير ذلك. تكون هذه المعلومات بصيغة DWARF المعيارية التي تُعَد مرادفًا لصيغة ELF تقريبًا.
يمكن أن يؤدي تضمين معلومات تنقيح الأخطاء إلى جعل الملفات والمكتبات القابلة للتنفيذ كبيرة جدًا، إذ لا تزال تشغل مساحة كبيرة على القرص الصلب بالرغم من أنها ليست مطلوبة في الذاكرة للتشغيل الفعلي، وبالتالي يجب إزالة هذه المعلومات من ملف ELF. يمكن نقل كل من الملفات التي أًزيلت منها هذه المعلومات والملفات التي لم تًُزال منها هذه المعلومات، ولكن توفّر معظم طرق توزيع أو نشر الملفات الثنائية binary distribution الحالية معلومات لتنقيح الأخطاء في ملفات منفصلة. يمكن استخدام أداة objcopy لاستخراج معلومات تنقيح الأخطاء (--only-keep-debug
) ثم إضافة رابط في الملف القابل للتنفيذ الأصلي إلى هذه المعلومات المُزالَة (--add-gnu-debuglink
)، ثم سيكون هناك قسم خاص بالاسم .gnu_debuglink
موجود في الملف القابل للتنفيذ الأصلي ويحتوي على قيمة فريدة بحيث يمكن لمنقح الأخطاء عند بدء جلسات تنقيح الأخطاء التأكدَ من أنه يربط معلومات تنقيح الأخطاء الصحيحة بالملف التنفيذي الصحيح.
يوضح المثال التالي إزالة معلومات تنقيح الأخطاء إلى ملفات منفصلة باستخدام الأداة objcopy:
$ gcc -g -shared -o libtest.so libtest.c $ objcopy --only-keep-debug libtest.so libtest.debug $ objcopy --add-gnu-debuglink=libtest.debug libtest.so $ objdump -s -j .gnu_debuglink libtest.so libtest.so: file format elf32-i386 Contents of section .gnu_debuglink: 0000 6c696274 6573742e 64656275 67000000 libtest.debug... 0010 52a7fd0a R...
تشغل الرموز مساحة أقل بكثير، ولكنها تُعَد هدفًا للإزالة من الخرج النهائي، إذ لن تكون هناك حاجة لبقاء معظم الرموز بمجرد ربط ملفات التعليمات المُصرَّفة object files لملف قابل للتنفيذ بالصورة النهائية. تُعَد الرموز مطلوبة لإصلاح مدخلات المنقولات Relocation، ولكن لن تكون الرموز بعد ذلك ضرورية تمامًا لتشغيل البرنامج النهائي. توفر سلسلة أدوات GNU لتجريد البرنامج في نظام لينكس خياراتٍ لإزالة الرموز. لاحظ أنه يجب تحليل بعض الرموز في وقت التشغيل (للربط الديناميكي Dynamic Linking)، ولكنها تُوضَع في جداول رموز ديناميكية منفصلة حتى لا تُزال وتجعل الخرج النهائي عديم الفائدة.
التفريغ الأساسي Coredump
يُعَد التفريغ الأساسي مجرد ملف ELF. يوضح المثال التالي مرونة صيغة ELF بوصفها صيغة ثنائية:
$ readelf --all ./core ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: CORE (Core file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 52 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 15 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0 There are no sections in this file. There are no sections to group in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align NOTE 0x000214 0x00000000 0x00000000 0x0022c 0x00000 0 LOAD 0x001000 0x08048000 0x00000000 0x01000 0x01000 R E 0x1000 LOAD 0x002000 0x08049000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x003000 0x489fc000 0x00000000 0x01000 0x1b000 R E 0x1000 LOAD 0x004000 0x48a17000 0x00000000 0x01000 0x01000 R 0x1000 LOAD 0x005000 0x48a18000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x006000 0x48a1f000 0x00000000 0x01000 0x153000 R E 0x1000 LOAD 0x007000 0x48b72000 0x00000000 0x00000 0x01000 0x1000 LOAD 0x007000 0x48b73000 0x00000000 0x02000 0x02000 R 0x1000 LOAD 0x009000 0x48b75000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x00a000 0x48b76000 0x00000000 0x03000 0x03000 RW 0x1000 LOAD 0x00d000 0xb771c000 0x00000000 0x01000 0x01000 RW 0x1000 LOAD 0x00e000 0xb774d000 0x00000000 0x02000 0x02000 RW 0x1000 LOAD 0x010000 0xb774f000 0x00000000 0x01000 0x01000 R E 0x1000 LOAD 0x011000 0xbfeac000 0x00000000 0x22000 0x22000 RW 0x1000 There is no dynamic section in this file. There are no relocations in this file. There are no unwind sections in this file. No version information found in this file. Notes at offset 0x00000214 with length 0x0000022c: Owner Data size Description CORE 0x00000090 NT_PRSTATUS (prstatus structure) CORE 0x0000007c NT_PRPSINFO (prpsinfo structure) CORE 0x000000a0 NT_AUXV (auxiliary vector) LINUX 0x00000030 Unknown note type: (0x00000200) $ eu-readelf -n ./core Note segment of 556 bytes at offset 0x214: Owner Data size Type CORE 144 PRSTATUS info.si_signo: 11, info.si_code: 0, info.si_errno: 0, cursig: 11 sigpend: <> sighold: <> pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544 utime: 0.000000, stime: 0.000000, cutime: 0.000000, cstime: 0.000000 orig_eax: -1, fpvalid: 0 ebx: 1219973108 ecx: 1243440144 edx: 1 esi: 0 edi: 0 ebp: 0xbfecb828 eax: 74565 eip: 0x080483c4 eflags: 0x00010286 esp: 0xbfecb818 ds: 0x007b es: 0x007b fs: 0x0000 gs: 0x0033 cs: 0x0073 ss: 0x007b CORE 124 PRPSINFO state: 0, sname: R, zomb: 0, nice: 0, flag: 0x00400400 uid: 1000, gid: 1000, pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544 fname: coredump, psargs: ./coredump CORE 160 AUXV SYSINFO: 0xb774f414 SYSINFO_EHDR: 0xb774f000 HWCAP: 0xafe8fbff <fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov clflush dts acpi mmx fxsr sse sse2 ss tm pbe> PAGESZ: 4096 CLKTCK: 100 PHDR: 0x8048034 PHENT: 32 PHNUM: 8 BASE: 0 FLAGS: 0 ENTRY: 0x8048300 UID: 1000 EUID: 1000 GID: 1000 EGID: 1000 SECURE: 0 RANDOM: 0xbfecba1b EXECFN: 0xbfecdff1 PLATFORM: 0xbfecba2b NULL LINUX 48 386_TLS index: 6, base: 0xb771c8d0, limit: 0x000fffff, flags: 0x00000051 index: 7, base: 0x00000000, limit: 0x00000000, flags: 0x00000028 index: 8, base: 0x00000000, limit: 0x00000000, flags: 0x00000028
يمكننا أن نرى في المثال السابق اختبارًا باستخدام أداة readelf
أولًا للملف الأساسي الناتج عن مثال إنشاء ملف تفريغ أساسي واستخدامه باستخدام منقح الأخطاء gdb. لا توجد أقسام أو منقولات أو معلومات أخرى غريبة في هذا الملف يمكن أن تكون مطلوبة لتحميل ملف قابل للتنفيذ أو مكتبة، إذ يتكون من سلسلة من ترويسات البرامج التي تمثل مقاطع LOAD
التي هي عمليات تفريغ بيانات أولية أنشأتها النواة Kernel لتخصيصات الذاكرة الحالية.
المكون الآخر لملف التفريغ الأساسي هو أقسام الملاحظات NOTE
التي تحتوي على البيانات الضرورية لتنقيح الأخطاء ولكن ليس بالضرورة أن تُلتقَط في لقطة مباشرة لتخصيصات الذاكرة. يوفّر برنامج eu-readelf
المُستخدَم في الجزء الثاني من المثال السابق رؤيةً أكثر اكتمالًا للبيانات من خلال فك تشفيرها.
تقدّم الملاحظة PRSTATUS
مجموعة من المعلومات حول العملية أثناء تشغيلها، فمثلًا يمكننا أن نرى من قيمة cursig
أن البرنامج تلقى إشارة قيمتها 11 تمثل خطأ تقطيع segmentation fault. كما تتضمن بالإضافة إلى معلومات رقم العملية ملف تفريغ لجميع المسجلات الحالية. يمكن لمنقح الأخطاء بالنظر إلى قيم المسجّل إعادة بناء حالة المكدس وبالتالي توفير تعقب خلفي Backtrace، حيث يمكن لمنقح الأخطاء جنبًا إلى جنب مع الرمز ومعلومات تنقيح الأخطاء من الملف الثنائي الأصلي إظهار كيفية الوصول إلى نقطة التنفيذ الحالية.
من المخرجات الأخرى المتجه المساعد الحالي Auxiliary Vector أو AUXV
اختصارًا. تصف 386_TLS
مدخلات جدول الواصفات العام المستخدمة في تقديم Implementation معمارية x86 لمخزن محلي قائم على الخيوط thread-local storage. سيكون هناك مدخلات مكررة لكل خيط قيد التشغيل بالنسبة للتطبيق متعدد الخيوط، حيث سيفهم منقح الأخطاء ذلك وهذه هي الطريقة التي يطبّق بها منقح الأخطاء gdb الأمر thread
لإظهار الخيوط والتبديل بينها.
تنشئ النواة ملف التفريغ الأساسي ضمن حدود إعدادات ulimit
الحالية، إذ يمكن أن يؤدي البرنامج الذي يستخدم قدرًا كبيرًا من الذاكرة إلى وجود ملف تفريغ كبير جدًا، ويُحتمَل أن يملأ القرص الصلب ويزيد المشاكل سوءًا، ويُضبَط ulimit
على مستوى منخفض أو حتى على القيمة الصفر لأن معظم الأشخاص الذين ليسوا مطورين لديهم استخدام ضئيل لملف التفريغ الأساسي. لكن يظل التفريغ الأساسي هو الطريقة الأكثر فائدة لتنقيح أخطاء حالة غير متوقعة بعد التعطل.
إنشاء أقسام مخصصة
يُعَد تنظيم الشيفرة والبيانات والرموز شيئًا يمكن للمبرمج ترك إعدادات سلسلة أدواته الافتراضية كما هي، ولكن يمكن في بعض الأحيان توسيع أو تخصيص الأقسام ومحتوياتها مثل وحدات نواة لينكس التي تُستخدَم لتحميل المشغّلات والميزات الأخرى ديناميكيًا في نواة التشغيل. تُعَد هذه الوحدات غير قابلة للنقل، فهي تعمل فقط مع إصدار بناء نواة ثابت واحد، لذلك يمكن أن تكون الواجهة بين الوحدات والنواة مرنةً وغير مرتبطة بمعايير معينة، وبالتالي يمكن تعريف طرق تخزين مثل تخزين معلومات الترخيص والتأليف والاعتماديات والمعاملات الخاصة بالوحدات بصورة فريدة وكاملة باستخدام النواة.
يمكن لأداة modinfo
فحص هذه المعلومات في وحدةٍ ما وتقديمها للمستخدم. يوضح المثال التالي استخدام مثال عن وحدة نواة لينكس fuse
التي تسمح لمكتبات مساحة المستخدم بتوفير تقديمات نظام الملفات للنواة:
$ cd /lib/modules/$(uname -r) $ sudo modinfo ./kernel/fs/fuse/fuse.ko filename: /lib/modules/3.2.0-4-amd64/./kernel/fs/fuse/fuse.ko alias: devname:fuse alias: char-major-10-229 license: GPL description: Filesystem in Userspace author: Miklos Szeredi <miklos@szeredi.hu> depends: intree: Y vermagic: 3.2.0-4-amd64 SMP mod_unload modversions parm: max_user_bgreq:Global limit for the maximum number of backgrounded requests an unprivileged user can set (uint) parm: max_user_congthresh:Global limit for the maximum congestion threshold an unprivileged user can set (uint) $ objdump -s -j .modinfo ./kernel/fs/fuse/fuse.ko ./kernel/fs/fuse/fuse.ko: file format elf64-x86-64 Contents of section .modinfo: 0000 616c6961 733d6465 766e616d 653a6675 alias=devname:fu 0010 73650061 6c696173 3d636861 722d6d61 se.alias=char-ma 0020 6a6f722d 31302d32 32390070 61726d3d jor-10-229.parm= 0030 6d61785f 75736572 5f636f6e 67746872 max_user_congthr 0040 6573683a 476c6f62 616c206c 696d6974 esh:Global limit 0050 20666f72 20746865 206d6178 696d756d for the maximum 0060 20636f6e 67657374 696f6e20 74687265 congestion thre 0070 73686f6c 6420616e 20756e70 72697669 shold an unprivi 0080 6c656765 64207573 65722063 616e2073 leged user can s 0090 65740070 61726d74 7970653d 6d61785f et.parmtype=max_ 00a0 75736572 5f636f6e 67746872 6573683a user_congthresh: 00b0 75696e74 00706172 6d3d6d61 785f7573 uint.parm=max_us 00c0 65725f62 67726571 3a476c6f 62616c20 er_bgreq:Global 00d0 6c696d69 7420666f 72207468 65206d61 limit for the ma 00e0 78696d75 6d206e75 6d626572 206f6620 ximum number of 00f0 6261636b 67726f75 6e646564 20726571 backgrounded req 0100 75657374 7320616e 20756e70 72697669 uests an unprivi 0110 6c656765 64207573 65722063 616e2073 leged user can s 0120 65740070 61726d74 7970653d 6d61785f et.parmtype=max_ 0130 75736572 5f626772 65713a75 696e7400 user_bgreq:uint. 0140 6c696365 6e73653d 47504c00 64657363 license=GPL.desc 0150 72697074 696f6e3d 46696c65 73797374 ription=Filesyst 0160 656d2069 6e205573 65727370 61636500 em in Userspace. 0170 61757468 6f723d4d 696b6c6f 7320537a author=Miklos Sz 0180 65726564 69203c6d 696b6c6f 7340737a eredi <miklos@sz 0190 65726564 692e6875 3e000000 00000000 eredi.hu>....... 01a0 64657065 6e64733d 00696e74 7265653d depends=.intree= 01b0 59007665 726d6167 69633d33 2e322e30 Y.vermagic=3.2.0 01c0 2d342d61 6d643634 20534d50 206d6f64 -4-amd64 SMP mod 01d0 5f756e6c 6f616420 6d6f6476 65727369 _unload modversi 01e0 6f6e7320 00 ons .
تحلّل الأداة modinfo
القسم .modinfo
المُضمَّن في ملف الوحدة لتقديم تفاصيلها. يوضح المثال التالي كيفية وضع حقل "المؤلف Author" في الوحدة، حيث تأتي هذه الشيفرة البرمجية غالبًا من include/linux/module.h
:
/* * ابدأ من الأسفل ثم انتقل إلى الأعلى */ /* وحدات الماكرو غير المباشرة مطلوبة للصق الوسيط المُوسَّع مثل الماكرو __LINE__ */ #define ___PASTE(a,b) a##b #define __PASTE(a,b) ___PASTE(a,b) #define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__) /* تحويل الماكرو غير المباشر إلى سلسلة نصية. يسمح إنشاء مستويين للمعامل بأن يكون ماكرو بحد ذاته، حيث يتحوّل __stringify(FOO) مثلًا إلى السلسلة "bar" عند التصريف باستخدام -DFOO=bar. */ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __attribute__((section(".modinfo"), unused, aligned(1))) \ = __stringify(tag) "=" info /* معلومات عامة للصيغة tag = "info" */ #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) /* * استخدم للمؤلفين المتعددين الذين يستخدمون "Name <email>" أو "Name" فقط تعليمات أو سطور MODULE_AUTHOR() متعددة * */ #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) /* ---- */ MODULE_AUTHOR("Your Name <your@name.com>");
لنبدأ من الجزء السفلي حيث نرى أن الوحدة MODULE_AUTHOR
تغلّف الماكرو الأعم __MODULE_INFO
، ويمكننا أن نرى أننا نبني متغيرًا من النوع static const char []
ليحتوي على السلسلة النصية "author=Your Name <your@name.com>"
. لاحظ أن المتغير لديه معامل إضافي __attribute__((section(".modinfo")))
يخبر المصرّف بعدم وضع هذا المتغير في قسم البيانات data
مع المتغيرات الأخرى، ولكن يمكن إخفاؤه في قسم ELF الخاص به الذي اسمه .modinfo
. توقِف المعاملات الأخرى المتغير الذي يجري تحسينه لأنه يبدو غير مُستخدَم وللتأكد من أننا نضع المتغيرات بجانب بعضها بعضًا من خلال تحديد المحاذاة.
هناك استخدام واسع النطاق لتحويل وحدات الماكرو إلى سلاسل نصية Stringification Macros، وهي حيل تُستخدَم في معالج لغة C المسبق لضمان أن السلاسل النصية والتعاريف يمكن أن تكون مع بعضها البعض. يوفّر المصرِّف gcc التعريف الخاص __COUNTER__
الذي يوفر قيمة فريدة ومتزايدة في كل استدعاء، مما يسمح باستدعاءات وحدة MODULE_AUTHOR
متعددة في ملف واحد دون استخدام اسم المتغير نفسه.
يمكننا فحص الرموز الموضوعة في الوحدة النهائية لمعرفة النتيجة النهائية كما يلي:
$ objdump --syms ./fuse.ko | grep modinfo 0000000000000000 l d .modinfo 0000000000000000 .modinfo 0000000000000000 l O .modinfo 0000000000000013 __UNIQUE_ID_alias1 0000000000000013 l O .modinfo 0000000000000018 __UNIQUE_ID_alias0 000000000000002b l O .modinfo 0000000000000011 __UNIQUE_ID_alias8 000000000000003c l O .modinfo 000000000000000e __UNIQUE_ID_alias7 000000000000004a l O .modinfo 0000000000000068 __UNIQUE_ID_max_user_congthresh6 00000000000000b2 l O .modinfo 0000000000000022 __UNIQUE_ID_max_user_congthreshtype5 00000000000000d4 l O .modinfo 000000000000006e __UNIQUE_ID_max_user_bgreq4 0000000000000142 l O .modinfo 000000000000001d __UNIQUE_ID_max_user_bgreqtype3 000000000000015f l O .modinfo 000000000000000c __UNIQUE_ID_license2 000000000000016b l O .modinfo 0000000000000024 __UNIQUE_ID_description1 000000000000018f l O .modinfo 000000000000002a __UNIQUE_ID_author0 00000000000001b9 l O .modinfo 0000000000000011 __UNIQUE_ID_alias0 00000000000001d0 l O .modinfo 0000000000000009 __module_depends 00000000000001d9 l O .modinfo 0000000000000009 __UNIQUE_ID_intree1 00000000000001e2 l O .modinfo 000000000000002f __UNIQUE_ID_vermagic0
سكربتات الرابط Linker Scripts
تتمثل وظيفة الرابط في بناء الأقسام Sections لتشكيل المقاطع Segments من خلال استخدام سكربت الرابط الذي يصِف مكان بدء المقاطع والأقسام الموجودة فيها ويحدد المعاملات الأخرى.
يوضح المثال الآتي مقتطفًا من سكربت الرابط الافتراضي الذي سيعرضه الرابط عند إعطاء الراية التفصيلية Verbose باستخدام الرايتين -Wl
و--verbose
مع gcc. السكربت الافتراضي مُضمَّنٌ في الرابط ويعتمد على تعريفات واجهة API المعيارية لإنشاء برامج عاملة لمساحة مستخدمٍ خاصة بمنصة البناء.
$ gcc -Wl,--verbose -o test test.c GNU ld (GNU Binutils for Debian) 2.26 ... using internal linker script: ================================================== OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") OUTPUT_ARCH(i386:x86-64) ENTRY(_start) SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); ... SECTIONS { /* أقسام للقراءة فقط مُدمَجة في مقطع النص text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; .interp : { *(.interp) } .note.gnu.build-id : { *(.note.gnu.build-id) } .hash : { *(.hash) } .gnu.hash : { *(.gnu.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .gnu.version : { *(.gnu.version) } .gnu.version_d : { *(.gnu.version_d) } .gnu.version_r : { *(.gnu.version_r) } .rela.dyn : { ... } PROVIDE (etext = .); .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } .rodata1 : { *(.rodata1) } ...
يمكنك أن ترى في المثال السابق كيف يحدد سكربت الرابط أمورًا متعددة مثل مواقع البدء والأقسام المراد تجميعها في مقاطع مختلفة. تُستخدَم الراية -Wl
لتمرير الراية --verbose
إلى الرابط عبر gcc، إذ يمكن توفير سكربتات مخصصة للرابط باستخدام الرايات. ليس مُحتمًلًا أن يحتاج مطورو مساحة المستخدم العادية إلى تجاوز سكربت الرابط الافتراضي، ولكن تتطلب التطبيقات المخصَّصة جدًا مثل عمليات بناء النواة سكربتات مخصصة للرابط في أغلب الأحيان.
ترجمة -وبتصرُّف- للقسمين ELF Executables و Extending ELF concepts من فصل Behind the process من كتاب Computer Science from the Bottom Up لصاحبه Ian Wienand.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.