dlopen(3)

الاسم
dlclose, dlopen, dlmopen - فتح وإغلاق كائن مشترك

المكتبة
مكتبة الربط الديناميكي (libdl، -ldl)

موجز
#include <dlfcn.h>

void *dlopen(const char *path, int flags);
int dlclose(void *handle);

#define _GNU_SOURCE
#include <dlfcn.h>

void *dlmopen(Lmid_t lmid, const char *path, int flags);

الوصف

  dlopen()
الدالة dlopen() تُحمّل ملف الكائن المشترك الديناميكي (المكتبة المشتركة) المُسمى بسلسلة المحارف المنتهية بصفر path وتُعيد "مقبضًا" غير شفاف للكائن المُحمّل. يُستخدم هذا المقبض مع دوال أخرى في واجهة برمجة تطبيقات dlopen، مثل dlsym(3) وdladdr(3) وdlinfo(3) وdlclose().

إذا كان path NULL، فإن المقبض المُعاد يكون للبرنامج الرئيس. إذا احتوى path على شرطة مائلة ("/")، فإنه يُفسر كاسم مسار (نسبي أو مطلق). وإلا، يبحث الرابط الديناميكي عن الكائن كما يلي (انظر ld.so(8) لمزيد من التفاصيل):

•   (ELF فقط) إذا احتوى الكائن المُستدعي (أي المكتبة المشتركة أو القابل للتنفيذ الذي يُستدعى منه dlopen()) على علامة DT_RPATH، ولم يحتوي على علامة DT_RUNPATH، فإن الأدلة المدرجة في علامة DT_RPATH تُبحث.

•   إذا، في وقت بدء البرنامج، كان المتغير البيئي LD_LIBRARY_PATH مُعرّفًا ليحتوي على قائمة أدلة مفصولة بنقطتين، فإن هذه تُبحث. (كإجراء أمني، يُتجاهل هذا المتغير للبرامج ذات معرف المستخدم المُعيَّن ومعرف المجموعة المُعيَّن.)

•   (ELF فقط) إذا احتوى الكائن المُستدعي على علامة DT_RUNPATH، فإن الأدلة المدرجة في تلك العلامة تُبحث.

•   ملف الخبيئة /etc/ld.so.cache (الذي يُحافظ عليه ldconfig(8)) يُفحص لمعرفة ما إذا كان يحتوي على إدخال لـ path.

•   الأدلة /lib و/usr/lib تُبحث (بهذا الترتيب).

إذا كان للكائن المُحدد بواسطة path تبعيات على كائنات مشتركة أخرى، فإن هذه تُحمّل آليًا بواسطة الرابط الديناميكي باستخدام نفس القواعد. (قد تحدث هذه العملية بشكل تكراري، إذا كانت تلك الكائنات بدورها لها تبعيات، وهكذا.)

يجب تضمين إحدى القيمتين التاليتين في flags:

RTLD_LAZY
    نفذ الربط الكسول. حل الرموز فقط عند تنفيذ الكود الذي يُشير إليها. إذا لم يُشر إلى الرمز أبدًا، فإنه لا يُحل أبدًا. (يُجرى الربط الكسول فقط لإشارات الدوال؛ إشارات المتغيرات تُربط فورًا عند تحميل الكائن المشترك.) منذ glibc 2.1.1، يُلغى هذا العلم بتأثير المتغير البيئي LD_BIND_NOW.

RTLD_NOW
    إذا حُددت هذه القيمة، أو عُيّن المتغير البيئي LD_BIND_NOW إلى سلسلة محارف غير فارغة، فإن جميع الرموز غير المُعرّفة في الكائن المشترك تُحل قبل أن تُعيد dlopen(). إذا لم يمكن فعل ذلك، تُعاد رسالة خطأ.

قد تُضاف صفر أو أكثر من القيم التالية باستخدام عملية OR في flags:

RTLD_GLOBAL
    ستُتاح الرموز المُعرَّفة بواسطة هذا الكائن المشترك لحل الرموز للكائنات المشتركة المُحمَّلة لاحقًا.

RTLD_LOCAL
    هذا هو عكس RTLD_GLOBAL، والمبدئي إذا لم يُحدَّد أي علم. الرموز المُعرَّفة في هذا الكائن المشترك لا تُتاح لحل المراجع في الكائنات المشتركة المُحمَّلة لاحقًا.

RTLD_NODELETE (منذ glibc 2.2)
    لا تُفرغ الكائن المشترك أثناء dlclose(). وبالتالي، لا تُعاد تهيئة المتغيرات الثابتة والعامة للكائن إذا أُعيد تحميل الكائن باستخدام dlopen() في وقت لاحق.

RTLD_NOLOAD (منذ glibc 2.2)
    لا تُحمِّل الكائن المشترك. يمكن استخدام هذا لاختبار ما إذا كان الكائن مقيمًا بالفعل (يُرجع dlopen() NULL إذا لم يكن، أو مقبض الكائن إذا كان مقيمًا). يمكن أيضًا استخدام هذا العلم لترقية الأعلام على كائن مشترك مُحمَّل بالفعل. على سبيل المثال، يمكن إعادة فتح كائن مشترك كان مُحمَّلاً سابقًا باستخدام RTLD_LOCAL باستخدام RTLD_NOLOAD | RTLD_GLOBAL.

RTLD_DEEPBIND (منذ glibc 2.3.4)
    ضَع نطاق البحث عن الرموز في هذا الكائن المشترك قبل النطاق العام. هذا يعني أن الكائن المستقل سيستخدم رموزه الخاصة بتفضيل على الرموز العامة التي تحمل الاسم نفسه والموجودة في الكائنات المُحمَّلة بالفعل.

إذا كان path NULL، فإن المقبض المُرجَع هو للبرنامج الرئيس. عند إعطائه إلى dlsym(3)، يتسبب هذا المقبض في بحث عن رمز في البرنامج الرئيس، متبوعًا بجميع الكائنات المشتركة المُحمَّلة عند بدء البرنامج، ثم جميع الكائنات المشتركة المُحمَّلة بواسطة dlopen() مع العلم RTLD_GLOBAL.

تُحل مراجع الرموز في الكائن المشترك باستخدام (بالترتيب): الرموز في خريطة الربط للكائنات المُحمَّلة للبرنامج الرئيس وتبعياته؛ الرموز في الكائنات المشتركة (وتبعياتها) التي فُتحت سابقًا باستخدام dlopen() باستخدام العلم RTLD_GLOBAL؛ والتعريفات في الكائن المشترك نفسه (وأي تبعيات حُمِّلت لذلك الكائن).

يمكن أيضًا استخدام أي رموز عامة في الملف التنفيذي وُضعت في جدول الرموز الديناميكي بواسطة ld(1) لحل المراجع في كائن مشترك مُحمَّل ديناميكيًا. قد تُوضع الرموز في جدول الرموز الديناميكي إما لأن الملف التنفيذي رُبط باستخدام العلم "-rdynamic" (أو، بالمرادف، "--export-dynamic")، الذي يتسبب في وضع جميع الرموز العامة للملف التنفيذي في جدول الرموز الديناميكي، أو لأن ld(1) لاحظ اعتمادًا على رمز في كائن آخر أثناء الربط الثابت.

إذا فُتح نفس الكائن المشترك مرة أخرى باستخدام dlopen()، يُرجَع نفس مقبض الكائن. يحتفظ الرابط الديناميكي بأعداد مراجع لمقابض الكائنات، لذلك لا يُحرر كائن مشترك مُحمَّل ديناميكيًا حتى يُستدعى dlclose() عليه بعدد مرات نجاح dlopen() عليه. تُستدعى المنشئات (انظر أدناه) فقط عندما يُحمَّل الكائن فعليًا في الذاكرة (أي عندما يزيد عدد المراجع إلى 1).

قد يُجبر استدعاء dlopen() لاحق يُحمِّل نفس الكائن المشترك باستخدام RTLD_NOW على حل الرموز لكائن مشترك حُمِّل سابقًا باستخدام RTLD_LAZY. وبالمثل، يمكن ترقية كائن فُتح سابقًا باستخدام RTLD_LOCAL إلى RTLD_GLOBAL في استدعاء dlopen() لاحق.

إذا فشل dlopen() لأي سبب، يُرجع NULL.

  dlmopen()
تُؤدي هذه الدالة نفس مهمة dlopen()—وسيطات path و flags، بالإضافة إلى القيمة المُرجَعة، هي نفسها، باستثناء الاختلافات المُشار إليها أدناه.

تختلف الدالة dlmopen() عن dlopen() بشكل أساسي في أنها تقبل وسيطة إضافية، lmid، تُحدد قائمة خريطة الربط (المشار إليها أيضًا باسم namespace) التي يجب تحميل الكائن المشترك فيها. (بالمقارنة، تُضيف dlopen() الكائن المشترك المُحمَّل ديناميكيًا إلى نفس مساحة الاسم مثل الكائن المشترك الذي يُستدعى منه dlopen().) النوع Lmid_t هو مقبض غير شفاف يُشير إلى مساحة اسم.

وسيطة lmid إما أن تكون معرف مساحة اسم موجودة (يمكن الحصول عليه باستخدام طلب dlinfo(3) RTLD_DI_LMID) أو إحدى القيم الخاصة التالية:

LM_ID_BASE
    حُمِّل الكائن المشترك في النطاق المبدئي (أي نطاق التطبيق).

LM_ID_NEWLM
    أنشئ نطاقًا جديدًا وحُمِّل الكائن المشترك في ذلك النطاق. يجب أن يكون الكائن قد وُصل بشكل صحيح للإشارة إلى جميع الكائنات المشتركة الأخرى التي يحتاجها، لأن النطاق الجديد فارغ مبدئيًا.

إذا كان المسار فارغًا (NULL)، فإن القيمة الوحيدة المسموح بها لـ lmid هي LM_ID_BASE.

  dlclose()
تقلل الدالة dlclose() عدد المراجع على الكائن المشترك المُحمَّل ديناميكيًا المشار إليه بـ المقبض.

إذا انخفض عدد مراجع الكائن إلى الصفر ولم تكن أي رموز في هذا الكائن مطلوبة بواسطة كائنات أخرى، فإن الكائن يُفرغ بعد استدعاء أي مدمرات مُعرَّفة للكائن أولاً. (قد تكون الرموز في هذا الكائن مطلوبة في كائن آخر لأن هذا الكائن فُتح مع العلم RTLD_GLOBAL وأحد رموزه حقق إعادة توطين في كائن آخر.)

جميع الكائنات المشتركة التي حُمّلت آليًا عند استدعاء dlopen() على الكائن المشار إليه بـ المقبض تُغلق بشكل تكراري بنفس الطريقة.

لا يضمن الإرجاع الناجح من dlclose() إزالة الرموز المرتبطة بـ المقبض من مساحة عنوان المستدعي. بالإضافة إلى المراجع الناتجة عن استدعاءات dlopen() الصريحة، قد يكون الكائن المشترك حُمِّل ضمنيًا (وتم عد مراجعه) بسبب التبعيات في كائنات مشتركة أخرى. فقط عندما تُحرر جميع المراجع يمكن إزالة الكائن المشترك من مساحة العنوان.

قيمة الإرجاع
عند النجاح، تُرجع dlopen() و dlmopen() مقبضًا غير فارغ (non-NULL) للكائن المُحمَّل. عند الخطأ (لم يُعثر على الملف، أو لم يكن قابلاً للقراءة، أو كان بتنسيق خاطئ، أو تسبب في أخطاء أثناء التحميل)، تُرجع هذه الدوال NULL.

عند النجاح، تُرجع dlclose() 0؛ عند الخطأ، تُرجع قيمة غير صفرية.

يمكن تشخيص الأخطاء من هذه الدوال باستخدام dlerror(3).

السمات
للاطلاع على شرح للمصطلحات المستخدمة في هذا القسم، انظر attributes(7).
الواجهة | السمة | القيمة
dlopen(), dlmopen(), dlclose() | سلامة الخيوط | MT-Safe

المعايير

dlopen()
dlclose()
    POSIX.1-2008.

dlmopen()
RTLD_NOLOAD
RTLD_NODELETE
    GNU.

RTLD_DEEPBIND
    سولاريس.

التاريخ

dlopen()
dlclose()
    glibc 2.0. POSIX.1-2001.

dlmopen()
    glibc 2.3.4.

ملاحظات

  dlmopen() والنطاقات
تعرّف قائمة خريطة الوصل نطاقًا معزولاً لحل الرموز بواسطة الواصل الديناميكي. داخل النطاق، تُحمّل الكائنات المشتركة التابعة ضمنيًا وفقًا للقواعد المعتادة، وتُحل مراجع الرموز بالمثل وفقًا للقواعد المعتادة، ولكن هذا الحل يقتصر على التعريفات المقدمة بواسطة الكائنات التي حُمّلت (صراحةً وضمنيًا) في النطاق.

تسمح الدالة dlmopen() بعزل تحميل الكائنات—أي القدرة على تحميل كائن مشترك في نطاق أسماء جديد دون كشف باقي التطبيق للرموز التي يوفرها الكائن الجديد. لاحظ أن استخدام العلم RTLD_LOCAL غير كافٍ لهذا الغرض، لأنه يمنع رموز الكائن المشترك من أن تكون متاحة لأي كائن مشترك آخر. في بعض الحالات، قد نرغب في جعل الرموز التي يوفرها كائن مشترك محمّل ديناميكيًا متاحة لمجموعة فرعية من الكائنات المشتركة الأخرى دون كشف هذه الرموز للتطبيق بأكمله. يمكن تحقيق ذلك باستخدام نطاق أسماء منفصل والعلم RTLD_GLOBAL.

يمكن أيضًا استخدام الدالة dlmopen() لتوفير عزل أفضل من العلم RTLD_LOCAL. على وجه الخصوص، قد تُرقّى الكائنات المشتركة المحمّلة بـ RTLD_LOCAL إلى RTLD_GLOBAL إذا كانت تابعة لكائن مشترك آخر محمّل بـ RTLD_GLOBAL. وبالتالي، فإن RTLD_LOCAL غير كافٍ لعزل كائن مشترك محمّل إلا في الحالة (النادرة) حيث يكون لدى المرء تحكم صريح في جميع تبعيات الكائن المشترك.

الاستخدامات المحتملة لـ dlmopen() هي الإضافات حيث لا يثق مؤلف إطار تحميل الإضافات بمؤلفي الإضافات ولا يرغب في حل أي رموز غير معرفة من إطار الإضافة إلى رموز الإضافة. استخدام آخر هو تحميل نفس الكائن أكثر من مرة. بدون استخدام dlmopen()، سيتطلب ذلك إنشاء نسخ متميزة من ملف الكائن المشترك. باستخدام dlmopen()، يمكن تحقيق ذلك بتحميل نفس ملف الكائن المشترك في نطاقات أسماء مختلفة.

يدعم تطبيق glibc حدًا أقصى يبلغ 16 نطاق أسماء.

  دوال التهيئة والإنهاء
قد تُصدّر الكائنات المشتركة دوال باستخدام سمات الدالة __attribute__((constructor)) و __attribute__((destructor)). تُنفّذ دوال البناء قبل أن تعود dlopen()، وتُنفّذ دوال التدمير قبل أن تعود dlclose(). قد يُصدّر الكائن المشترك عدة بناة ومدمّرات، ويمكن ربط أولويات بكل دالة لتحديد الترتيب الذي تُنفّذ به. راجع صفحات معلومات gcc (تحت "سمات الدالة") لمزيد من المعلومات.

طريقة أقدم لتحقيق نفس النتيجة (جزئيًا) هي عبر استخدام رمزين خاصين يتعرف عليهما الرابط: _init و _fini. إذا صَدّر كائن مشترك محمّل ديناميكيًا روتينًا باسم _init()، فسيُنفّذ هذا الكود بعد تحميل الكائن المشترك، قبل أن تعود dlopen(). إذا صَدّر الكائن المشترك روتينًا باسم _fini()، فسيُستدعى هذا الروتين قبل تفريغ الكائن مباشرة. في هذه الحالة، يجب تجنب الربط مع ملفات بدء تشغيل النظام، التي تحتوي على إصدارات مبدئية من هذه الملفات؛ يمكن فعل ذلك باستخدام خيار سطر الأوامر gcc(1) -nostartfiles.

استخدام _init و _fini أصبح الآن مهملاً لصالح البناة والمدمّرين المذكورين أعلاه، والذين، من بين مزايا أخرى، يسمحون بتعريف دوال تهيئة وإنهاء متعددة.

منذ glibc 2.2.3، يمكن استخدام atexit(3) لتسجيل معالج خروج يُستدعى آليًا عند تفريغ كائن مشترك.

  السجل
هذه الدوال جزء من واجهة برمجة تطبيقات dlopen، المشتقة من SunOS.

العلل
اعتبارًا من glibc 2.24، يُولّد تحديد العلم RTLD_GLOBAL عند استدعاء dlmopen() خطأ. علاوة على ذلك، يُؤدي تحديد RTLD_GLOBAL عند استدعاء dlopen() إلى تعطل البرنامج (SIGSEGV) إذا تم الاستدعاء من أي كائن محمّل في نطاق أسماء غير النطاق الأولي.

أمثلة
يُحمّل البرنامج أدناه مكتبة الرياضيات (glibc)، ويبحث عن عنوان الدالة cos(3)، ويطبع جيب التمام لـ 2.0. فيما يلي مثال لبناء البرنامج وتشغيله:

    $ cc dlopen_demo.c -ldl;
    $ ./a.out;
    -0.416147

  مصدر البرنامج
    #include <dlfcn.h>
    #include <stdio.h>
    #include <stdlib.h>

    #include <gnu/lib-names.h>  /* Defines LIBM_SO (which will be a
                                   string such as "libm.so.6") */
    int
    main(void)
    {
        void *handle;
        typeof(double (double)) *cosine;
        char *error;

        handle = dlopen(LIBM_SO, RTLD_LAZY);
        if (!handle) {
            fprintf(stderr, "%s\n", dlerror());
            exit(EXIT_FAILURE);
        }

        dlerror();    /* Clear any existing error */

        cosine = (typeof(double (double)) *) dlsym(handle, "cos");

        /* According to the ISO C standard, casting between function
           pointers and 'void *', as done above, produces undefined results.
           POSIX.1-2001 and POSIX.1-2008 accepted this state of affairs and
           proposed the following workaround:

               *(void **) &cosine = dlsym(handle, "cos");

           This (clumsy) cast conforms with the ISO C standard and will
           avoid any compiler warnings.

           The 2013 Technical Corrigendum 1 to POSIX.1-2008 improved matters
           by requiring that conforming implementations support casting
           'void *' to a function pointer.  Nevertheless, some compilers
           (e.g., gcc with the '-pedantic' option) may complain about the
           cast used in this program.  */

        error = dlerror();
        if (error != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(EXIT_FAILURE);
        }

        printf("%f\n", (*cosine)(2.0));
        dlclose(handle);
        exit(EXIT_SUCCESS);
    }

انظر أيضًا
ld(1), ldd(1), pldd(1), dl_iterate_phdr(3), dladdr(3), dlerror(3), dlinfo(3), dlsym(3), rtld-audit(7), ld.so(8), ldconfig(8)

صفحات معلومات gcc، صفحات معلومات ld

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.