SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-21

لماذا لا يعمل ملف Linux: glibc أم musl؟

ظهور الخطأ GLIBC_2.34 not found يعني أن بيئة البناء فرضت الحد الأدنى للإصدار. تعرّف إلى ما يطلبه الملف واختر طريقة من أربع لشحنه بتوافق أفضل.

لماذا لا يعمل ملف Linux الثنائي على خادم أقدم

يصعب إنتاج ملف Linux ثنائي قابل للنقل لأن glibc، وهي مكتبة GNU C، تضمن التوافق في اتجاه واحد فقط. يستمر ملف ثنائي قديم في العمل مع glibc أحدث. ولا يعمل ملف ثنائي أحدث مع glibc أقدم. يحدد الجهاز الذي تُجري عليه عملية التجميع الحد الأدنى لكل جهاز تنشر عليه الملف.

توضح رسالة الخطأ الإصدار المطلوب تحديداً:

./mytool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by ./mytool)

لا يوجد تلف أو خطأ في الإعدادات. قرأ المحمّل الديناميكي وسم إصدار مسجلاً داخل الملف الثنائي، ثم بحث عن هذا الوسم في libc الخاصة بالنظام. ولأنه لم يعثر عليه، رفض بدء البرنامج. لن يغيّر تنزيل الملف من جديد وتشغيل chmod +x شيئاً، لأن المتطلب مكتوب داخل الملف نفسه. إما أن تغيّر طريقة إنشاء الملف الثنائي، أو تغيّر ما تنشره.

ما الذي يفعله إصدار الرموز في glibc فعلياً

تحمل كل دالة يصدّرها glibc وسم إصدار. printf داخل libc.so.6 هو في الواقع printf@@GLIBC_2.2.5. عندما يغيّر glibc سلوك دالة أو ABI الخاص بها، فإنه لا يستبدل الدالة القديمة. بل يُبقي الشيفرة القديمة تحت الوسم القديم، ويضيف الشيفرة الجديدة تحت وسم جديد، ولذلك يحتوي libc.so.6 واحد على عدة إصدارات من الرمز نفسه في الوقت ذاته. يعثر ملف ثنائي من عام 2009 على وسم 2009 الذي لا يزال موجوداً، ولهذا تعمل التوافقية مع الإصدارات الأحدث بكفاءة كبيرة.

تسجّل خطوة الربط ما استخدمته. يحصل ملفك الثنائي على قسم .gnu.version_r يوضّح، بمعنى ما، «أحتاج إلى GLIBC_2.38 من libc.so.6». لم تكن تلك العلامة موجودة في libc أقدم، لذلك يوقف المحمّل التنفيذ قبل تشغيل main. لا توجد آلية بديلة، لأن توفيرها يعني تمرير دالة مختلفة إلى البرنامج بصمت عن الدالة التي جرى تجميعه ضدها.

أكثر الأسباب شيوعاً منذ 2021 هو glibc 2.34. دمج ذلك الإصدار libpthread وlibdl في libc، ونقل __libc_start_main، وهي الدالة التي تبدأ تشغيل كل برنامج C، إلى وسم GLIBC_2.34. لذلك يطلب برنامج hello-world مُجمّع على أي توزيعة تحتوي على glibc 2.34 أو إصدار أحدث GLIBC_2.34، رغم أن الشيفرة المصدرية لا تستدعي أي شيء حديث. ولهذا ظهرت المشكلة فجأة لدى أشخاص لم تتغير شيفرتهم الخاصة منذ سنوات.

ما إصدار glibc الذي يأتي مع توزيعتك؟

Chartglibc version in each distribution's own repositories
The data behind this chart
[
  {
    "distro": "CentOS 7",
    "glibc": 2.17
  },
  {
    "distro": "RHEL 8",
    "glibc": 2.28
  },
  {
    "distro": "Ubuntu 20.04",
    "glibc": 2.31
  },
  {
    "distro": "Debian 11",
    "glibc": 2.31
  },
  {
    "distro": "RHEL 9",
    "glibc": 2.34
  },
  {
    "distro": "Ubuntu 22.04",
    "glibc": 2.35
  },
  {
    "distro": "Debian 12",
    "glibc": 2.36
  },
  {
    "distro": "Ubuntu 24.04",
    "glibc": 2.39
  },
  {
    "distro": "Debian 13",
    "glibc": 2.41
  }
]

هذه هي الإصدارات التي توفّرها كل توزيعة في مستودعاتها الخاصة، وفقاً لما نشرته التوزيعات وما كان سارياً في August 2026. انتهى دعم CentOS 7 منذ وقت طويل، لكنه يبقى في القائمة لأن خوادم موروثة لا تزال تشغّله. افحص أي جهاز لديك باستخدام ldd --version، الذي يطبع إصدار glibc في سطره الأول، أو باستخدام getconf GNU_LIBC_VERSION.

اقرأ هذه الصفوف البالغ عددها 9 كسُلّم. أنشئ البرنامج على Debian 13 باستخدام glibc 2.41، وستعمل النتيجة على Debian 13 وما بعده فقط. أنشئه على Ubuntu 20.04 باستخدام glibc 2.31، وسيعمل المصدر نفسه على كل صف أعلى من ذلك السطر، بما في ذلك جهاز Debian 13. أقدم خادم يجب أن تدعمه هو مضيف البناء الوحيد المهم، لذلك تحدد التوزيعة التي تعتمدها الحد الأدنى لـABI لكل ما تترجمه من مصدر لهذا الخادم. من المفيد حسم هذا القرار قبل إنشاء الأسطول، إلى جانب المفاضلات الأخرى في اختيار نظام التشغيل لتشغيله على VPS.

كيف أجد الحد الأدنى من إصدار glibc الذي يحتاج إليه ملف ثنائي؟

استخرج هذه المعلومة من الملف نفسه. تعمل هذه الطريقة مع أي ملف، بما في ذلك ملف ثنائي سلّمك إياه مورّد من دون ملاحظات عن بنائه.

objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5

السطر الأخير هو الحد الأدنى المطلوب. إذا لم يكن objdump موجوداً، ثبّت الحزمة binutils. وإذا تعذّر عليك تثبيت أي شيء على ذلك الخادم، فسيعطيك strings -a ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 نتيجة قريبة، لأن العلامات مخزّنة كسلاسل نصية عادية داخل الملف.

يعرض readelf -V ./mytool المتطلب نفسه بتنسيق منظّم. ابحث عن الكتلة التي عنوانها Version needs section '.gnu.version_r'. وهي تسرد سطراً واحداً من Name: GLIBC_x.y لكل علامة، وتجمعها تحت المكتبة التي يجب أن توفرها.

لمعرفة الدالة التي حدّدت الحد الأدنى، ابحث عن العلامة باستخدام grep: objdump -T ./mytool | grep GLIBC_2.38. غالباً ما تكون رمزاً واحداً، وأحياناً يمكنك تجنّب استخدامه. إذا لم يطبع objdump -T أي شيء على الإطلاق، فالملف الثنائي مرتبط بشكل ثابت، ولذلك لا يحتوي على جدول رموز ديناميكي ولا على متطلب إصدار يمكن طباعته.

ما الذي يخبرك به file وreadelf -d عن ملف ثنائي سلّمك إياه شخص ما

يُظهر file البنية ونوع الربط في سطر واحد.

file ./mytool

يظهر إصدار glibc العادي كما يلي:

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped

يشير الإصدار الثابت إلى statically linked ولا يسمّي مفسّراً. أما إصدار musl فيسمّي مفسّراً مختلفاً:

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, ...

المفسّر هو الحقل المهم. وهو محمّل ديناميكي يشغّله kernel قبل برنامجك، ويجب أن يكون هذا المسار المحدد موجوداً على الجهاز الهدف. لا يكون /lib/ld-musl-x86_64.so.1 موجوداً على خادم Debian أو Ubuntu إلا إذا ثبّت شخص ما musl عليه.

يسرد readelf -d ./mytool المكتبات المشتركة التي يتطلبها الملف الثنائي، حسب الاسم:

Dynamic section at offset 0x2d58 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)   Shared library: [libssl.so.3]
 0x0000000000000001 (NEEDED)   Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)  Library runpath: [$ORIGIN/../lib]

كل إدخال NEEDED هو متطلب إلزامي، وتتم مطابقته حسب soname. يمثّل libssl.so.3 OpenSSL 3، لذلك لن يُحمّل هذا الملف الثنائي على خادم لا يحتوي إلا على libssl.so.1.1، وsoname هو السبب: اختلاف soname يعني وجود ABI غير متوافق عمداً. يحدد RUNPATH المكان الذي يبحث فيه محمّل البرامج أولاً، بينما يتمدد $ORIGIN إلى الدليل الذي يحتوي على الملف الثنائي، وبذلك تعثر الحزمة المستقلة على مكتباتها الخاصة.

لا تشغّل ldd على ملف ثنائي لا تثق به. في glibc، يمكن أن يحل ldd التبعيات عبر تشغيل البرنامج باستخدام المحمّل، ما يتيح لملف خبيث تنفيذ التعليمات البرمجية. أما readelf وobjdump فلا يقرآن إلا البايتات. قبل كل ذلك، تأكد من أن التنزيل هو الملف الذي نشره المشروع فعلياً، لأن التحقق من التنزيل مقابل checksum المنشور له هو الخطوة الوحيدة التي تخبرك بمن يملك البايتات التي بين يديك.

الأخطاء التي ستراها فعلياً، وما يعنيه كل خطأ

يسمّي المُحمِّل إصداراً من GLIBC لا يستطيع العثور عليه. جرى تجميع الملف التنفيذي باستخدام glibc أحدث من الإصدار الموجود على النظام الهدف. لا يؤدي تثبيت أي شيء على النظام الهدف إلى حل المشكلة بأمان. اختر أحد الخيارات الأربعة أدناه.

تعرض الصدفة No such file or directory لملف يمكنك رؤيته بوضوح. العنصر المفقود هو المُفسِّر، وليس الملف التنفيذي. تعيد execve القيمة ENOENT عندما يكون مسار المُحمِّل مفقوداً من ترويسة ELF، وتطبع الصدفة الرسالة الوحيدة المتاحة لها. شغّل file واقرأ مسار المُفسِّر. ينتج الملف التنفيذي المرتبط بـmusl هذا العَرَض نفسه تماماً على خادم يحتوي على glibc فقط.

cannot execute binary file: Exec format error يعني أن المعمارية غير صحيحة: ملف تنفيذي x86-64 على خادم arm64، أو العكس. يوضح السطر الأول من ناتج file المعمارية التي يستخدمها الملف.

error while loading shared libraries: libssl.so.3: cannot open shared object file يعني أن مكتبة NEEDED مفقودة أو موجودة باسم soname مختلف. ثبّت حزمة التوزيعة المطابقة. تختلف أسماء الحزم بين عائلات التوزيعات، لذلك ترجم الأمر قبل نسخ سطر apt من ملف README، وتغطي المكافئات بين أوامر dnf وapt عملية المطابقة هذه.

ظهور Segmentation fault فقط من عملية إنشاء باستخدام musl تعمل بشكل سليم تحت glibc. يكون السبب عادةً حجم مكدس مؤشرات الترابط، وهو مشروح ضمن الخيار 2.

أربع طرق لإصدار ملف ثنائي محمول لنظام Linux

كل خيار يفرض تكلفة فعلية. اختر الطريقة وفقاً لما يفعله برنامجك أثناء التشغيل، لا وفقاً للطريقة التي تبدو أنظف.

الخيار 1: ابنِ على أقدم توزيعة تدعمها

إنه الحل الأقل إثارة، وغالباً ما يكون الحل الصحيح. نفّذ الترجمة داخل صورة حاوية لأقدم توزيعة تتعهد بدعمها. لا يستطيع الرابط تسجيل سوى الوسوم التي يوفّرها glibc القديم فعلياً، لذلك ينخفض الحد الأدنى إلى ذلك الإصدار، بينما تظلّ الثنائيات ثنائيات ديناميكية عادية مع الاحتفاظ بكل سلوك glibc.

docker run --rm -v "$PWD:/src" -w /src ubuntu:20.04 sh -c 'apt-get update && apt-get install -y build-essential && make'

يعمل الناتج على glibc 2.31 وعلى كل إصدار أحدث منه. تحقّق بدلاً من الافتراض: شغّل الأمر ذي السطر الواحد objdump -T الوارد سابقاً على الناتج، وتأكد من أن أعلى وسم هو الوسم المتوقع.

تكمن التكلفة في سلسلة الأدوات. تحمل صورة الأساس القديمة أيضاً مترجماً قديماً، وقد يسبب ذلك مشكلة عندما يحتاج الكود إلى معيار C++ حديث. يتجنب Go وRust هذه المشكلة غالباً، لأن سلاسل أدواتهما تُثبّت داخل الحاوية بشكل مستقل عن حزم التوزيعة. بالنسبة إلى C وC++، يمكنك إضافة مترجم أحدث من قناة سلسلة الأدوات الخاصة بالتوزيعة، أو استخدام صور manylinux، التي صُممت تحديداً للجمع بين glibc قديم جداً وGCC حديث. يمكن لمترجم C المضمّن في Zig أيضاً استهداف إصدار glibc محدد مباشرة، كما في zig cc -target x86_64-linux-gnu.2.28، ما يوفّر الحد الأدنى نفسه من دون الاحتفاظ بصورة قديمة.

الخيار 2: الربط الثابت مع musl

musl مكتبة C صغيرة صُممت مع مراعاة الربط الثابت. يحتوي ملف musl الثنائي الثابت على libc الخاصة به، ولا يحدد مفسراً، ويعمل على أي نواة Linux بالبنية الصحيحة. بهذه الطريقة تُبنى معظم تنزيلات الأدوات ذات الملف الواحد.

sudo apt install -y musl-tools
musl-gcc -static -O2 hello.c -o hello
file ./hello

يجب أن يعرض file الآن statically linked من دون حقل للمفسر. في Rust، أضف الهدف وابنِ البرنامج له:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

لا يحتاج Go إلى أي من ذلك. باستخدام CGO_ENABLED=0 go build تكون النتيجة ثابتة بالفعل، ولا تربط أي libc على الإطلاق.

تتغير عمليات البحث في NSS. يحل glibc أسماء المستخدمين والمضيفين من خلال NSS ‏(محوّل خدمة الأسماء)، الذي يحمّل وحدات libnss_* باستخدام dlopen أثناء تشغيل البرنامج. لا يستطيع glibc المرتبط ثابتاً فعل ذلك، ويحذرك الرابط بهذه الكلمات:

warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

يتجنب musl هذا التحذير لأنه لا ينفذ NSS أصلاً. لديه محلل أسماء خاص به، ويقرأ /etc/resolv.conf و/etc/hosts مباشرة. هذا مناسب وأبسط على VPS عادي. لكن على مضيف تأتي فيه الحسابات أو الأسماء من LDAP أو SSSD، لن يرى ملفك الثنائي هذه الحسابات أو الأسماء، بينما تراها كل البرامج الأخرى على الخادم. كما أن محلل الأسماء في musl أحدث عهداً من محلل glibc؛ فقد أضيف الرجوع إلى TCP لردود DNS الأكبر من 512 بايت في musl 1.2.4 عام 2023، لذلك تقتطع عمليات البناء باستخدام إصدارات musl الأقدم الإجابات الكبيرة.

لا يعمل dlopen. في ملف musl الثنائي الثابت، يكون dlopen مجرد stub يفشل دائماً. كل ما يحمّل شيفرة أثناء التشغيل غير متاح: أنظمة الإضافات، ووحدات PAM، وبرامج تشغيل GPU، ووحدات مجموعة محارف iconv الخاصة بـ glibc. إذا كان برنامجك يحتاج إلى dlopen، فالربط الثابت غير ممكن، وتحتاج إلى أحد الخيارات الثلاثة الأخرى.

تصبح تحديثات الأمان مسؤوليتك. يلتقط الملف الثنائي المرتبط ديناميكياً إصلاحاً في libc فور تشغيل الخادم لتحديث حزمته. أما الملف الثنائي الثابت فلا يفعل ذلك أبداً. عندما يظهر إدخال CVE ‏(الثغرات والتعرّضات الشائعة) متعلقاً بـlibc الخاصة بك، أو بـOpenSSL الثابتة التي ضمّنتها، يجب أن تعيد البناء والتوزيع. وتظل كل نسخة منشورة مسبقاً عرضة للخطر إلى أن يستبدل أحدهم الملف. احتفظ بسجل لما ربطته داخل الملف، لأن لا شيء على النظام المستهدف يستطيع إبلاغ المسؤول بأن ملفك الثنائي ذي الملف الواحد يحتوي على مكتبة من عامين مضيا.

يتغير الترخيص. تخضع glibc لترخيص LGPL، ويؤدي الربط الثابت إلى تفعيل التزام إعادة الربط في LGPL؛ إذ يجب أن تزوّد المستلمين بما يحتاجون إليه لإعادة ربط برنامجك بإصدار مختلف من glibc. أما musl فتخضع لترخيص MIT ولا تفرض هذا الشرط. وهذا هو السبب الرئيسي لاختيار المشاريع التي توزّع ملفات ثنائية ذات ملف واحد لـmusl بدلاً من glibc الثابتة.

يؤدي اختلافان في وقت التشغيل إلى أعطال مربكة. يبلغ حجم مكدس الخيط الافتراضي في musl مقدار 128 KiB، مقابل 8 MiB في glibc. لذلك يتعرض الكود الذي يضع مخزناً مؤقتاً كبيراً في مكدس الخيط لخطأ segmentation fault من دون رسالة أو سطر في السجل. حدّد الحجم صراحةً باستخدام pthread_attr_setstacksize، أو انقل المخزن المؤقت إلى heap. كما أن موزّع الذاكرة في musl مصمم للأحجام الصغيرة والسلوك المتوقع، وليس لتخصيصات متزامنة كثيرة من خيوط متعددة. لذلك قد تعمل البرامج المعتمدة على التخصيصات والمبنية على خيوط أبطأ بشكل ملحوظ. اختبر عبء العمل الخاص بك بدلاً من الاعتماد على سمعة أي من المكتبتين.

الخيار 3: تضمين أداة التحميل والمكتبات

اشحن المكتبات بجوار الملف الثنائي مع أداة التحميل المطابقة، ثم شغّل البرنامج عبر أداة التحميل هذه.

./ld-linux-x86-64.so.2 --library-path ./lib ./mytool

لجعل ذلك دائماً، اكتب المسارات في الملف باستخدام patchelf:

patchelf --set-interpreter "$PWD/lib/ld-linux-x86-64.so.2" --set-rpath '$ORIGIN/lib' ./mytool

تحدد قاعدة واحدة ما إذا كان هذا سينجح: يجب أن تأتي أداة التحميل وlibc.so.6 من بنية glibc نفسها. فهما زوج متطابق، ويؤدي خلط أداة تحميل المضيف مع libc المضمّنة إلى تعطل أثناء بدء التشغيل بدلاً من ظهور خطأ واضح. ضمّن كليهما أو لا تضمّن أياً منهما.

يستخدم AppImage هذا النمط بعد تغليفه، إذ يضع الحمولة في صورة squashfs ويستخدم runtime صغيراً لربطها. وهناك خاصية يغفل عنها كثيرون: لا يضمّن AppImage glibc، لذلك يظل AppImage المبني على توزيعة حديثة يفشل بالخطأ نفسه المتعلق بالإصدار على خادم قديم. توصي إرشادات AppImage نفسها بالبناء على أقدم قاعدة تدعمها، ما يجعل هذا تنسيق تسليم مبنياً فوق الخيار 1، لا بديلاً عنه.

على خادم بلا واجهة، يحتاج AppImage إلى FUSE (نظام الملفات في مساحة المستخدم) لربط حمولته، وغالباً لا تتضمنه صورة VPS المصغّرة. وتذكر رسالة الفشل libfuse.so.2. يمكنك تجاوز عملية الربط بالكامل باستخدام ./App.AppImage --appimage-extract-and-run، إذ يفك ذلك الحزمة إلى مجلد مؤقت وينفذ البرنامج منه.

الخيار 4: شحن صورة حاوية

انقل بيئة المستخدم بالكامل مع البرنامج. تحتوي الصورة على libc الخاصة بها، لذلك لا تعود glibc الموجودة على المضيف مؤثرة، ولا يلزم سوى توافق النواة والبنية. هذا الخيار الأقل تعقيداً، وهو يلغي فئة المشكلة بالكامل، ولذلك توزَّع به كمية كبيرة من برامج الخوادم. تُعد تشغيل Docker على VPS الطريقة المعتادة لاستخدامها.

تظل نواة المضيف تفرض حداً. تستخدم glibc الأحدث استدعاءات نظام أحدث، ويمكن لمضيف حاويات قديم حظرها: تستخدم glibc 2.34 والإصدارات الأحدث clone3، وترفضها ملفات تعريف seccomp الافتراضية الأقدم. يتمثل العَرَض في فشل فوري مع Operation not permitted من دون أي ذكر لـ glibc. يؤدي تحديث وقت تشغيل الحاويات على المضيف إلى إصلاح المشكلة، لأن الحظر موجود في مرشح استدعاءات النظام الخاص بوقت التشغيل، وليس في النواة.

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

ما الخيار الذي ينبغي أن تختاره؟

  • أداة داخلية للخوادم التي تتحكم فيها، والتي تشغّل جميعها توزيعة واحدة: أنشئها ديناميكياً على تلك التوزيعة وتوقف عند ذلك.
  • ملف واحد ينزّله أشخاص لا تعرفهم ويشغّلونه: استخدم musl الثابتة، إذا كان البرنامج لا يحتاج إلى dlopen ولا إلى عمليات بحث تعتمد على NSS.
  • برنامج يحتوي على إضافات أو يحتاج إلى الوصول إلى GPU: أبقه ديناميكياً، وأنشئه على أساس قديم، واستخدم الخيار 3 لتسليمه.
  • خدمة طويلة التشغيل على جهاز يحتوي بالفعل على بيئة تشغيل: سلّم الصورة.

أيّاً كان اختيارك، دوّنه وتحقق منه. أصبحت glibc الموجودة على مضيف الإنشاء جزءاً من عملية الإصدار لديك. كما أن ترقية جهاز إنشاء من إصدار LTS إلى الإصدار التالي ترفع الحد الأدنى المطلوب بصمت، وتتسبب في تعطل برامج المستخدمين الذين كانت تعمل لديهم في الشهر الماضي. ثبّت صورة الإنشاء باستخدام وسم، وأثبت أعلى وسم GLIBC_ في الناتج باعتباره خطوة من خطوات الإنشاء، حتى يفشل التحقق في مسار CI لديك بدلاً من أن يفشل في طرفية شخص آخر.

FAQ

لماذا أحصل على الرسالة "version GLIBC_2.38 not found" على خادمي؟

جُمِّع الملف الثنائي على جهاز يحتوي على glibc أحدث من الإصدار الموجود على خادمك. يوفّر نظام إصدار الرموز في glibc توافقاً أمامياً فقط: تعمل الملفات الثنائية القديمة على glibc الحديثة، ولا تعمل الملفات الثنائية الحديثة على glibc القديمة، لأن المحمّل يحتاج إلى وسم الإصدار المحدد المسجّل في الملف، ولأن libc الأقدم لم يتضمن هذا الوسم قط. لا يوجد شيء يمكنك تثبيته على الخادم لإصلاح ذلك بأمان. أعد البناء باستخدام base أقدم، أو وفّر إصداراً ثابتاً مبنياً باستخدام musl، أو أرفق المكتبات مع المحمّل المطابق لها، أو وفّر container image.

كيف أعرف إصدار glibc الذي يحتاج إليه ملف ثنائي؟

اقرأ وسوم الإصدار من الملف باستخدام objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5. يوضّح السطر الأخير أقدم إصدار glibc يمكنه تحميله. يعرض readelf -V ./mytool المتطلبات نفسها في قسم .gnu.version_r، مجمّعةً حسب المكتبة التي يجب أن توفرها. إذا لم يطبع objdump -T أي شيء، فالملف الثنائي ثابت ولا يتطلب glibc إطلاقاً. قارن النتيجة بالإصدار الموجود على خادمك، الذي تحصل عليه من ldd --version.

هل الملف الثنائي المبني باستخدام musl أبطأ من الملف الثنائي المبني باستخدام glibc؟

يعتمد ذلك على عبء العمل، والإجابة الدقيقة هي القياس. يبدأ الملف الثنائي الثابت المبني باستخدام musl بسرعة أكبر، لأنه لا يحتاج إلى تشغيل محمّل ولا إلى تنفيذ عمل relocation وقت exec. في المقابل، صُمّم allocator في musl للحجم الصغير والسلوك المتوقع، وليس للتعامل مع خيوط كثيرة تجري عمليات allocation في الوقت نفسه. كما أن musl يحتوي على عدد أقل من إجراءات السلاسل والذاكرة المحسّنة يدوياً مقارنةً بـ glibc، لذلك قد تكون البرامج متعددة الخيوط التي تعتمد بكثافة على allocation أو السلاسل أبطأ بشكل ملحوظ. اختبر برنامجك على خادمك قبل اعتماد أي من هذين الاستنتاجين.

لماذا يقول bash "No such file or directory" لملف موجود؟

الملف المفقود هو dynamic loader، وليس الملف الثنائي نفسه. يقرأ kernel مسار interpreter من ترويسة ELF ويعيد ENOENT عندما يكون هذا المسار مفقوداً، ثم يعرض shell الرسالة الوحيدة المتاحة له. شغّل file ./mytool واقرأ الحقل interpreter. إذا أظهر /lib/ld-musl-x86_64.so.1 على خادم Debian أو Ubuntu، فهذا يعني أن لديك ملفاً ثنائياً مرتبطاً بـmusl على نظام يستخدم glibc، ولذلك تحتاج إلى التنزيل المتوافق مع musl أو إلى إصدار ثابت.

هل يمكنني نسخ libc.so.6 من خادم أحدث لإصلاح المشكلة؟

لا. يشكّل libc.so.6 وld-linux-x86-64.so.2 زوجاً متطابقاً من عملية بناء واحدة لـglibc، وتستخدمهما كل عملية على الجهاز. لذلك قد يؤدي استبدال النسخة النظامية إلى منع الخادم من تشغيل أي شيء، بما في ذلك الأدوات التي ستحتاج إليها للتراجع عن التغيير. إذا اضطررت إلى تشغيل ملف ثنائي أحدث على مضيف قديم، ففكّ glibc الأحدث في دليل خاص، ثم شغّل ذلك البرنامج عبر المحمّل الخاص به باستخدام --library-path. يؤثر ذلك في تلك العملية فقط. يظل الحل الأقل تسبباً في مفاجآت بعد ستة أشهر هو إعادة البناء باستخدام glibc الأقدم.

#glibc#musl#static-linking#portability#binaries