PDB (قاعدة بيانات البرنامج) ما هي؟ ── فهم معلومات التصحيح والرموز (symbols) وSource Link

· آخر تحديث: · · .NET, CSharp, VisualStudio, PDB, Debugging, Symbols, SourceLink, Diagnostics, التشغيل, الاستفادة من الأصول القائمة

1. ما ينبغي معرفته أوّلاً

عند بناء تطبيق .NET أو C++، قد يُنشأ ملفّ .pdb إلى جانب .dll أو .exe.

على سبيل المثال، يكون الناتج كهذا:

MyApp.exe
MyApp.dll
MyApp.pdb

وحين تستمرّ في التطوير دون أن تعرف ما هو هذا .pdb، تظهر تساؤلات من هذا القبيل:

  • هل يجوز وضع .pdb في بيئة الإنتاج
  • هل يتوقّف التطبيق عن العمل دون .pdb
  • هل من الغريب أن يظهر .pdb رغم أنّه بناء Release
  • هل يحتوي .pdb على الشيفرة المصدريّة كاملةً
  • هل يمكن دائماً نصب نقاط التوقّف (breakpoints) طالما وُجد .pdb
  • لماذا يلزم .pdb في تحليل الملفّات التفريغيّة (dump) وتحقيق الأعطال
  • كيف يُتعامَل مع .pdb في حزم NuGet
  • ما الفرق بين Source Link وخادم الرموز (symbol server) و.snupkg

لا يكون PDB بطلاً في التنفيذ اليوميّ. لكنّه مهمّ جدّاً في تحقيق الأعطال، وتحليل الملفّات التفريغيّة، وتوزيع المكتبات، وقابليّة قراءة سجلّات التشغيل، وتجربة التصحيح.

نكتب الخلاصة أوّلاً:

PDB هو ملفّ معلومات تصحيح يربط بين الملفّ التنفيذيّ أو التجميعة (assembly) وبين الشيفرة المصدريّة. ليس هو الجسم الذي يُشغِّل التطبيق، بل هو ما يُخبر أداة التصحيح وأدوات التشخيص بـ«أيّ تعليمة تقابل أيّ سطر مصدريّ» و«أيّ متغيّر محلّي هو ماذا» و«أيّ مصدر ينبغي النظر إليه».

في هذا المقال، لن نتناول PDB كمجرّد «ملفّ إضافيّ للتصحيح»، بل ننظّمه من زاوية كيفيّة التعامل معه كمُنتَج (deliverable) في العمل الفعليّ.

2. ما هو PDB

PDB اختصار لـ Program Database، وتُترجَم إلى العربيّة بـ قاعدة بيانات البرنامج. وكثيراً ما يُطلَق على ملفّ .pdb اسم ملفّ الرموز (symbol file).

الرمز (symbol)، ببساطة، هو معلومة عن اسم أو موضع داخل البرنامج. على سبيل المثال:

  • اسم الدالّة
  • اسم الدالّة العضويّة (method)
  • اسم المتغيّر المحلّي
  • اسم المعامل (argument)
  • معلومات النوع (type)
  • اسم ملفّ المصدر
  • رقم السطر في المصدر
  • التطابق بين الموضع في المصدر والتعليمة بعد الترجمة
  • معلومات يستخدمها أداة التصحيح لوضع نقطة توقّف
  • معلومات جلب المصدر الخاصّة بـ Source Link

أثناء كتابة الشيفرة المصدريّة، توجد أسماء مفهومة للبشر على هذا النحو.

public decimal CalculateTotalPrice(Order order)
{
    var subtotal = order.Lines.Sum(x => x.Price * x.Quantity);
    var tax = subtotal * 0.10m;
    return subtotal + tax;
}

لكنّ .dll أو .exe بعد البناء ليسا الشيفرة المصدريّة نفسها. في .NET، يصبحان IL وبيانات وصفيّة (metadata)، وفي C++ الأصليّة (native)، يصبحان ثنائيّاً قريباً من لغة الآلة.

نتيجةً لذلك، تصبح هذه المعلومات غير مفهومة كفايةً، أو يصعب فهمها، بالاعتماد على الملفّ التنفيذيّ وحده.

هذا الجزء من لغة الآلة / IL، لأيّ سطر من أيّ ملفّ .cs يقابل؟
هذا العنوان، لأيّ موضع من أيّ دالّة يقابل؟
ما اسم هذا المتغيّر المحلّي أصلاً؟
أين ينبغي فعليّاً وضع هذه نقطة التوقّف من حيث موضع التعليمة؟
لأيّ مصدر يقابل هذا الإطار (frame) في المكدّس (stack)؟

PDB هو الملفّ الذي يسدّ هذه الفجوة.

3. هل PDB ضروريّ للتشغيل

عادةً، لا يكون PDB ضروريّاً لتشغيل التطبيق. يكفي وجود .dll أو .exe كي يبدأ التطبيق تشغيله. وعدم وجود .pdb لا يعني أنّ المعالجة الاعتياديّة تصبح غير قابلة للتنفيذ.

لكن دون PDB، تصبح هذه الأمور صعبة:

ما تريد فعله ما يصعب فعله دون PDB
التنفيذ خطوة بخطوة بدقّة في Visual Studio لا يمكن التطابق بين سطر المصدر وموضع التنفيذ
نصب نقطة توقّف (breakpoint) لا يُعرَف موضع التعليمة المقابل، وقد تبقى نقطة التوقّف غير محلولة
إظهار اسم الملفّ ورقم السطر في تتبّع مكدّس الاستثناء لا تظهر معلومات رقم السطر، أو تظهر ناقصة
تحليل ملفّ تفريغيّ (dump) يصعب قراءة المكدّس والمتغيّرات والأنواع
الدخول خطوة بخطوة (step into) إلى مكتبة خارجيّة لا يمكن الربط بمصدر المكتبة
قراءة انهيار native تبقى فقط العناوين، دون معرفة اسم الدالّة أو موضعها

بعبارة أخرى، PDB ليس «ملفّاً للتشغيل»، بل «ملفّاً للتحقيق».

هذا الفارق مهمّ. عند وقوع عطل في الإنتاج، يستمرّ التطبيق في العمل دون PDB، لكنّ المحقِّق يقع في حيرة دونه. لذا يجب حفظ PDB دائماً كمُنتَج بناء، بصرف النظر عن وضعه في بيئة التشغيل من عدمه.

4. ما الذي يُسعِدنا في وجود PDB

عند وجود PDB، يسهُل على أداة التصحيح وأدوات التشخيص إرجاع الملفّ الثنائيّ إلى معلومات يقرأها البشر.

على سبيل المثال، قد تظهر معلومات انهيار دون PDB على هذا النحو:

MyApp.dll!0x00007ff9a1234567
MyApp.dll!0x00007ff9a1234abc
MyApp.dll!0x00007ff9a1234def

وحين يُحمَّل PDB بشكل صحيح، يصبح المعروض إلى هذا الحدّ.

MyApp.Services.OrderService.CalculateTotalPrice(Order order) Line 42
MyApp.Controllers.OrderController.Post(CreateOrderRequest request) Line 87
MyApp.Program.Main(string[] args) Line 16

هذا الفارق كبير.

الأوّل يحتاج إلى بدء التحقيق من العنوان. والثاني يمكن الوصول منذ البداية إلى «أيّ دالّة عند أيّ سطر».

في تحقيق الأعطال، يكون المهمّ هو مدى القدرة على الاقتراب من السبب خلال أوّل 30 دقيقة. مجرّد بقاء PDB يُغيِّر تماماً نقطة انطلاق التحقيق.

5. ما الذي يُحتوى داخل PDB

تختلف المعلومات المُحتواة في PDB باختلاف اللغة والمترجم (compiler) وصيغة PDB وإعدادات البناء. لذا لا يمكن الجزم ببساطة بأنّ «PDB يحتوي دائماً على كذا».

لكن، بحسّ مطوّري .NET، يُتوقَّع غالباً وجود هذه المعلومات تقريباً.

التطابق بين ملفّ المصدر والشيفرة بعد الترجمة
رقم السطر في المصدر
رموز الدوال أو الدوال العضويّة
أسماء المتغيّرات المحليّة
معلومات النطاق (scope)
مسار ملفّ المصدر أو checksum الخاصّ به
معلومات Source Link
الشيفرة المصدريّة المضمَّنة في بعض الحالات

والمهمّ بوجه خاصّ هو التطابق بين الموضع في المصدر والموضع وقت التنفيذ.

قد يتحوّل سطر واحد كُتب بلغة C# إلى تعليمات متعدّدة في IL أو في الشيفرة الأصليّة بعد JIT. وعلى العكس، قد يُجمَع تحسين (optimization) أسطر مصدريّة متعدّدة، أو تختفي، أو يظهر أنّ ترتيبها تغيّر.

يستخدم أداة التصحيح معلومات PDB للحكم على «سطر المصدر الذي ينبغي عرضه الآن».

6. ما لا يُحتوى داخل PDB

بخصوص PDB، فهم ما لا يُحتوى فيه أفضل من فهم ما يُحتوى فيه، إذ يقلّل ذلك من سوء الفهم.

عادةً، PDB ليس هذه الأشياء:

  • ليس جسم التطبيق نفسه
  • ليس ملفّ وقت تشغيل (runtime) ضروريّاً للتنفيذ
  • ليس نسخة احتياطيّة كاملة لكامل الشيفرة المصدريّة
  • ليس بديلاً عن مستودع Git
  • لا يُعيد بناء كامل إعدادات البناء أو معلومات البيئة بالكامل
  • لا يشرح سبب الخلل تلقائيّاً بمفرده

لكن، ثمّة نقطة تنبيه.

قد يحتوي PDB على مسار ملفّ المصدر، واسم النوع، واسم الدالّة، واسم المتغيّر المحلّي، وأحياناً معلومات Source Link أو الشيفرة المصدريّة المضمَّنة.

لذا لا يمكن القول إنّ PDB «ليس الشيفرة المصدريّة نفسها، فلا داعي للقلق من نشره كيفما كان».

قد يحتوي على اسم المشروع الداخليّ، ومسار يتضمّن اسم المستخدم، وبنية المجلّدات الداخليّة للشركة، وأسماء أنواع غير مُعلَنة، وأسماء يمكن منها استنتاج منطق العمل.

7. سوء فهم شائع 1: وجود PDB يُبطئ بيئة الإنتاج

مجرّد وضع PDB بجانب الملفّ لا يُبطئ المعالجة الاعتياديّة للتطبيق.

يُستخدَم PDB عندما يحتاج أداة التصحيح أو أدوات التشخيص إلى معلومات الرموز. لا تعمل المعالجة الاعتياديّة للتطبيق وهي تقرأ PDB في كلّ مرّة.

بالطبع، يُستخدَم في مواقف مثل حلّ اسم الملفّ ورقم السطر في تتبّع مكدّس الاستثناء، وإرفاق أداة التصحيح، وقراءة الرموز من قِبل أداة تحليل الأداء (profiler) أو أدوات التشخيص.

لكنّ فهم «وجود PDB يُبطئ الأمور باستمرار، لذا يجب عدم وضعه في الإنتاج مطلقاً» فهمٌ فَجّ.

في العمل الفعليّ، من الآمن التفكير على هذا النحو.

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

8. سوء فهم شائع 2: لا حاجة لـ PDB في بناء Release

PDB مفيد في بناء Release أيضاً. بل إنّ ما يلزم في تحقيق أعطال الإنتاج هو تحديداً PDB الخاصّ ببناء Release.

إذا كان ما يعمل في الإنتاج هو بناء Release، فامتلاك PDB الخاصّ ببناء Debug عديم الجدوى. وما يحتاجه أداة التصحيح هو PDB الذي أُنشئ لحظة بناء ذلك الملفّ الثنائيّ الفعليّ الخاصّ بالإنتاج.

والمهمّ هنا هو هذا التمييز.

البند المعنى
Debug / Release إعداد البناء من حيث التحسين (optimization)، والترجمة الشرطيّة (conditional compilation)، وإعدادات الناتج
وجود PDB من عدمه هل تُنشأ معلومات التصحيح وتُحفظ أم لا
سهولة التصحيح تتحدّد وفق وجود التحسين، ومحتوى PDB، وتطابق المصدر، وسلوك JIT وغيرها

نظراً لأنّ بناء Release مُحسَّن (optimized) غالباً، يصبح التنفيذ خطوة بخطوة أقلّ وضوحاً من بناء Debug. قد تختفي المتغيّرات المحليّة بفعل التحسين، أو لا يتوقّف التنفيذ بترتيب أسطر المصدر.

مع ذلك، يسهُل الحصول على هذه المعلومات إن وُجد PDB.

  • سطر المصدر الذي وقع فيه الاستثناء
  • أسماء الدوال على المكدّس
  • الموضع المقابل عند تحليل الملفّ التفريغيّ
  • أسماء الدوال في نتائج تحليل الأداء (profiling)
  • التطابق بين السجلّ والمصدر

فالأمر ليس «لا حاجة لـ PDB لأنّه Release»، بل العكس تماماً. لأنّه Release، يلزم الحفاظ على PDB المقابل لذلك البناء بالذات.

9. سوء فهم شائع 3: طالما وُجد PDB، يمكن تصحيح أيّ ملفّ ثنائيّ

لا يمكن إعادة استخدام PDB لأيّ .dll أو .exe كيفما كان.

يتحقّق أداة التصحيح من تطابق الملفّ الثنائيّ المستهدَف مع PDB. فإن استُخدم PDB غير مطابق قسراً، تنزاح المطابقة بين سطر المصدر والدالّة والمتغيّر.

على سبيل المثال، في هذه الحالات، قد يكون PDB موجوداً لكن غير قابل للاستخدام، أو عديم الجدوى.

محاولة تطبيق PDB أُعيد بناؤه محليّاً على DLL الإنتاج
نفس رقم الإصدار، لكن مبنيّ فعليّاً من commit مختلف
محاولة تحميل PDB ما قبل hotfix على DLL بعد hotfix
اختلاف إعدادات التحسين أو الترجمة الشرطيّة

ينبغي التفكير في PDB لا على أنّه «يتطابق تقريباً طالما المصدر نفسه»، بل «يجب أن يقابل نفس مُنتَج البناء».

لذا الأساس في CI/CD هو الحفظ بهذه الوحدة.

معرّف الـ commit
رقم البناء
رقم إصدار المُنتَج
.dll / .exe
.pdb
معلومات مرجعيّة للمصدر

المهمّ هو عدم كسر هذا التجميع.

10. سوء فهم شائع 4: طالما وُجد PDB، يمكن القراءة الكاملة دون الشيفرة المصدريّة

حتّى مع وجود PDB، لا يعني ذلك أنّ الشيفرة المصدريّة موجودة فيه بالضرورة.

يحمل PDB أساساً معلومات تربط بين المصدر والملفّ الثنائيّ. يمكنه حمل مسار ملفّ المصدر أو checksum الخاصّ به، ومعلومات Source Link، لكن لا يحمل PDB العاديّ دائماً نصّ المصدر كاملاً.

لذا، إن أردت الدخول خطوة بخطوة إلى مكتبة خارجيّة عبر أداة التصحيح، تحتاج إلى أحد هذه.

وجود نفس ملفّ المصدر لديك محليّاً
إمكانيّة جلب مصدر الـ commit الصحيح عبر Source Link
مصدر مضمَّن في PDB
الاستعاضة بمصدر مُفكَّك (decompiled)

يملك Visual Studio أيضاً وظيفة تفكيك (decompile) التجميعات (assemblies) الخاصّة بـ .NET وعرضها. لكنّ ناتج التفكيك ليس المصدر الأصليّ نفسه. تُفقَد التعليقات، والمسافات البيضاء، وأسماء المتغيّرات المحليّة، والأسلوب الأصليّ للكتابة، وشروط المعالج المسبق (preprocessor)، أو تتغيّر.

مفيد للاستخدام في التحقيق، لكن من الأفضل عدم الوثوق به بشكل مُفرط كبديل عن المصدر الأصليّ.

11. PDB وتتبّع المكدّس (stack trace)

في تتبّع مكدّس استثناء .NET، يتغيّر المعروض تبعاً لوجود PDB من عدمه.

حتّى دون PDB، قد يظهر اسم الدالّة العضويّة (method) أو اسم النوع (type). لأنّ تجميعة (assembly) .NET تحتوي بيانات وصفيّة (metadata).

لكن إن أردت إظهار اسم الملفّ ورقم السطر، يصبح PDB مهمّاً.

على سبيل المثال، دون PDB، يميل تتبّع المكدّس إلى أن يكون هكذا.

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order)
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request)

وإن وُجد PDB وأمكن حلّ رقم السطر، يتغيّر إلى هذا.

System.InvalidOperationException: Order is invalid
   at MyApp.Services.OrderService.Validate(Order order) in /src/MyApp/Services/OrderService.cs:line 42
   at MyApp.Controllers.OrderController.Post(CreateOrderRequest request) in /src/MyApp/Controllers/OrderController.cs:line 87

عندما يظهر هذا الفارق أثناء التشغيل، تتغيّر سرعة التحقيق كثيراً.

لكن، قد يشكِّل ظهور مسار الملفّ نفسه إفشاءً للمعلومات أحياناً. فيلزم اتّخاذ تدابير أخرى، مثل عدم إخراج تتبّع المكدّس في استجابة الخطأ الموجَّهة للخارج، وتقييد مكان حفظ السجلّات.

12. PDB وتحليل الملفّات التفريغيّة (dump)

اللحظة التي يبدو فيها PDB أكثر امتناناً هي عند تحليل الملفّات التفريغيّة (dump).

لنفترض وقوع مشكلة من هذا القبيل في بيئة الإنتاج.

  • انهارت العمليّة
  • بقي استخدام المعالج (CPU) مرتفعاً
  • يبدو أنّه توقّف تام (deadlock)
  • الذاكرة تستمرّ في الازدياد
  • لا تعود استجابة
  • انهيار عند حدود مع مكتبة native

في هذه الحالة، يُلتقَط ملفّ تفريغيّ (dump) ويُحلَّل.

لكن الملفّ التفريغيّ وحده لا يكفي. ما يحتويه الملفّ التفريغيّ هو حالة العمليّة في تلك اللحظة. ولتحويل المكدّس (stack) والوحدات (modules) الظاهرة فيه إلى أسماء وأسطر مصدريّة يفهمها البشر، يلزم PDB المقابل.

في .NET، يُستخدَم dotnet-dump وVisual Studio وWinDbg وSOS للتحليل. وعند وجود جزء native، تصبح إعدادات الرموز في WinDbg مهمّة.

نذكر إخفاقات شائعة في تحليل الملفّات التفريغيّة.

DLL الإنتاج موجود لكن دون PDB
PDB موجود لكنّه شيء آخر أُعيد بناؤه محليّاً
تعذّر تحميل رموز Windows / وقت تشغيل .NET
PDB الخاصّ بتطبيقنا موجود لكن رموز مكتبة طرف ثالث غير موجودة
مسار الرموز غير مضبوط، فلا يجد أداة التصحيح PDB

قد لا يسع الوقت للتجهيز بعد وقوع المشكلة في تحليل الملفّات التفريغيّة. المهمّ هو حفظ PDB لحظة البناء، وجعله قابلاً للاستخراج وقت التحقيق.

13. Windows PDB وPortable PDB

توجد صيغ متعدّدة لـ PDB.

الممثِّلان اللذان ينبغي فهمهما في العمل الفعليّ هما هذان الاثنان.

النوع السياق الرئيسيّ الخصائص
Windows PDB Visual C++، تصحيح Windows التقليديّ صيغة تُستخدَم كثيراً في تطوير Windows الأصليّ (native)
Portable PDB .NET / .NET Core فما بعده صيغة مُوجَّهة لـ .NET يمكن التعامل معها عبر منصّات متعدّدة

منذ .NET Core فصاعداً، يصبح Portable PDB مهمّاً. Portable PDB صيغة يمكن التعامل معها ليس فقط على Windows، بل أيضاً على Linux وmacOS.

قد تصادف Windows PDB في مشاريع عهد .NET Framework أو إعدادات Visual Studio قديمة. في مشاريع .NET SDK الحاليّة النمط، يتزايد اعتبار Portable PDB الافتراضيّ المُتوقَّع عادةً.

والمهمّ الانتباه إليه هنا هو أنّ الامتداد كلاهما .pdb.

الامتداد وحده لا يُفصح عن الفارق في الصيغة. عند ذِكر «PDB»، تأكّد من أيّ سياق يُقصَد.

هل الحديث عن Portable PDB لـ .NET؟
هل الحديث عن Windows PDB لـ Visual C++؟
هل الحديث عن مشروع قديم من .NET Framework؟
هل الحديث عن PDB الموزَّع عبر NuGet؟
هل الحديث عن الرموز المستخدَمة في WinDbg؟

14. DebugType في .NET

في مشاريع C#، يمكن تحديد طريقة إخراج معلومات التصحيح عبر DebugType.

القيم النموذجيّة هي كالتالي.

DebugType المعنى
portable يُنشئ Portable PDB كملفّ منفصل
embedded يُضمِّن معلومات التصحيح المكافئة لـ Portable PDB داخل .dll / .exe
full يُنشئ PDB بالصيغة الافتراضيّة للمنصّة الحاليّة
pdbonly لا فرق فعليّاً عن full منذ C# 6.0 فصاعداً
none لا يُنشئ PDB

في مشاريع .NET SDK الحاليّة النمط، القيمة الافتراضيّة لـ DebugType الخاصّ بـ C# هي portable في كلّ من Debug وRelease. لذا لا حاجة عادةً إلى تحديد DebugType صراحةً فقط لإخراج Portable PDB.

معنى كتابته صراحةً يظهر عندما تريد الإعلان عن «تثبيت هذه الصيغة» كسياسة للمشروع أو للمؤسّسة، أو إبراز الفارق مع مشروع قديم، أو اختيار سياسة مختلفة عن القيمة الافتراضيّة مثل embedded / none.

في مكتبات NuGet أو التوزيع الخارجيّ، يُنظَر في اختيار أحد portable أو embedded أو .snupkg.

على سبيل المثال، لإخراج Portable PDB صراحةً، تُكتَب هكذا.

<PropertyGroup>
  <DebugType>portable</DebugType>
</PropertyGroup>

ولتضمين PDB داخل التجميعة (assembly) بدل جعله ملفّاً منفصلاً، تُكتَب هكذا.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

وإن أردتَ بشدّة عدم إخراج PDB في بناء Release، يمكن كتابة هذا.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

لكن ينبغي حسم هذا بحذر. ضبط عدم إخراج PDB الخاصّ بـ Release قد يُوقعك أنت نفسك في مأزق عند تحقيق أعطال الإنتاج.

15. هل يكفي DebugSymbols=false وحده

عند الرغبة في إيقاف إنشاء PDB، قد تصادف مثالاً بضبط DebugSymbols إلى false.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugSymbols>false</DebugSymbols>
</PropertyGroup>

لكن إن كانت النيّة منع إنشاء PDB بشكل مؤكَّد، فضبط DebugType إلى none أوضح.

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DebugType>none</DebugType>
</PropertyGroup>

هذا الإعداد يُستخدَم أحياناً كسياسة توزيع للمكتبة أو التطبيق. لكن، نكرِّر، عدم إنشاء PDB يُنقِص من القدرة على تحقيق الأعطال.

في العمل الفعليّ، كثيراً ما يُقسَّم على هذا النحو.

يُنشأ PDB دائماً كمُنتَج بناء
يُحسَم وضعه في خادم الإنتاج بشكل منفصل
حتّى إن لم يُوضَع، يُحفَظ في مُنتَجات CI أو خادم الرموز

16. PDB لـ C++ يختلف قليلاً عن .NET

سياق PDB في C++ يختلف قليلاً عن PDB في .NET.

في Visual C++، يُنشأ PDB عبر خيارات مثل /Zi أو /ZI. كذلك، يتعلّق الأمر بـ PDB الذي يستخدمه المترجم (compiler)، وPDB الذي ينشئه الرابط (linker) في النهاية لأجل .exe / .dll.

في C++، يُعدّ PDB مهمّاً جدّاً لقراءة عناوين الشيفرة الأصليّة (native)، والدوال، والأنواع، والمتغيّرات المحليّة، والفرد (inline) بعد الفرد، والموضع بعد التحسين.

كذلك، دون PDB، يميل تحقيق أعطال native إلى الوصول إلى هذه الحالة.

يُعرَف عنوان الاستثناء
يُعرَف اسم الوحدة (module)
لكن لا يُعرَف اسم الدالّة ولا سطر المصدر

في التطبيقات التي تختلط فيها C++ وC#، وP/Invoke، وC++/CLI، وتطبيقات .NET التي تستدعي DLL أصليّة، يلزم ليس فقط PDB الخاصّ بجانب .NET، بل PDB الخاصّ بالجانب native أيضاً.

17. الرموز العامّة (public symbols) والرموز الخاصّة (private symbols)

في عالم رموز Windows، يوجد تمييز بين الرموز العامّة (public symbols) والرموز الخاصّة (private symbols).

الفارق تقريباً هو هذا.

النوع صورة المعلومات المُحتواة
private symbols معلومات شبه كاملة تشمل المتغيّرات المحليّة، والأنواع، والمعاملات، ومعلومات داخليّة تفصيليّة
public symbols معلومات مقتصرة على أسماء الدوال والعناوين وما شابه، مُوجَّهة للنشر

في الرموز الموزَّعة للخارج، قد يُسقَط private symbols ويُقتصَر على public symbols فقط.

الهدف من ذلك هو الموازنة بين قابليّة التصحيح ونطاق إفشاء المعلومات.

على سبيل المثال، قد تريد إظهار أسماء الدوال الحدّ الأدنى لتحليل انهيار منتَج شركتك. لكن لا تريد إظهار أسماء المتغيّرات المحليّة الداخليّة أو معلومات الأنواع. في مثل هذه الحالة، يُستخدَم أحياناً PDB مُجرَّد (stripped PDB).

في تطوير native الموجَّه لـ Windows، يُستخدَم أحياناً PDBCopy لإنشاء PDB بعد إزالة الرموز الخاصّة.

في المقابل، يلزم حفظ PDB كامل لتحقيق الأعطال الداخليّ. فإن بقي فقط PDB المُقتصَر للخارج، تقع في مأزق عند التحقيق العميق.

18. من أين يبحث أداة التصحيح عن PDB

يبحث Visual Studio أو WinDbg عن PDB من عدّة مواضع.

من الأماكن النموذجيّة هذه.

مجلّد ناتج المشروع
نفس مجلّد .dll / .exe
المسار الأصليّ لـ PDB المسجَّل داخل .dll / .exe
المجلّد المحدَّد في إعدادات الرموز الخاصّة بـ Visual Studio
ذاكرة تخزين الرموز المؤقّتة المحلّيّة (local symbol cache)
خادم الرموز الداخليّ للشركة
Microsoft Symbol Server
NuGet.org Symbol Server
خوادم رموز مثل Azure Artifacts

إن وُجد PDB لكن لم يُحمَّل، تحقَّق من هذه النقاط بالترتيب.

هل PDB مطابق للملفّ الثنائيّ المستهدَف
هل PDB موجود ضمن مسار بحث أداة التصحيح
هل يمكن الوصول إلى خادم الرموز
هل بقي شيء قديم في الذاكرة المؤقّتة المحلّيّة
هل تحميل رموز الوحدة المستهدَفة مُعطَّل
هل يُعامَل كشيفرة خارجيّة بسبب إعداد Just My Code

في Visual Studio، يمكن أثناء التصحيح النظر إلى نافذة Modules لمعرفة حالة تحميل الرموز لكلّ وحدة.

Debug
  Windows
    Modules

هنا، انظر إلى Symbol Status الخاصّ بالـ DLL المستهدَف.

الشائع هو هذه المعروضات الأربعة.

Symbols loaded.
Cannot find or open the PDB file.
PDB does not match image.
Skipped loading symbols.

إن تعثّرت بشأن PDB، فالنظر إلى نافذة Modules أوّلاً هو الطريق الأقصر.

19. ما هو خادم الرموز (symbol server)

خادم الرموز آليّة تجعل ملفّات الرموز مثل PDB قابلة للجلب من قِبل أداة التصحيح عند الحاجة إليها.

يمكن التشغيل إلى حدّ ما بمجرّد وضع PDB في مجلّد مشترَك ببساطة. لكن مع تزايد الإصدارات، ينهار الأمر سريعاً.

PDB الخاصّ بـ MyApp v1.0.0
PDB الخاصّ بـ MyApp v1.0.1
PDB الخاصّ بـ MyApp v1.0.1 hotfix
PDB الخاصّ بـ MyApp v1.1.0-beta
PDB بإعداد مختلف خاصّ بالعميل A فقط

يظهر عشرات من MyApp.pdb بنفس اسم الملفّ.

يُنظِّم خادم الرموز ملفّات PDB لا كأسماء ملفّات فحسب، بل استناداً إلى معلومات التطابق مع الملفّ الثنائيّ. لذا يسهُل على أداة التصحيح البحث عن «PDB الذي يناسب هذا الـ DLL».

مثال على بنية يمكن التفكير فيها في العمل الفعليّ.

Microsoft Symbol Server
  يُستخدَم لجلب رموز Windows أو وقت تشغيل .NET وغيرها

NuGet.org Symbol Server
  يُستخدَم لجلب رموز حزم NuGet العامّة

خادم الرموز الداخليّ للشركة
  يحفظ PDB الخاصّ بتطبيقات ومكتبات الشركة نفسها

ذاكرة تخزين الرموز المؤقّتة المحلّيّة
  يُعيد استخدام PDB الذي جُلب مرّة لتسريع التصحيح

إن كنتَ تحقِّق أعطال إنتاج في خدمة داخليّة، فتجهيز آليّة يُصدر بها CI ملفّات PDB إلى مستودع الرموز الداخليّ مفيد.

Source Link آليّة تربط بين PDB ونظام إدارة المصدر.

حتّى لو عرف PDB أنّ «هذا الموضع يقابل ملفّ المصدر هذا»، إن لم يكن ذلك الملفّ المصدريّ موجوداً لديك، لا يستطيع أداة التصحيح عرضه.

مع وجود Source Link، يستطيع أداة التصحيح باستخدام المعلومة المُحتواة في PDB جلب ملفّ المصدر المقابل لذلك الـ commit من GitHub أو Azure Repos أو GitLab أو Bitbucket وغيرها.

بعبارة أخرى، ما يحلّه Source Link هو مشكلة من هذا القبيل.

تريد الدخول خطوة بخطوة داخل مكتبة حُصل عليها من NuGet
لم تستنسخ (clone) مصدر تلك المكتبة لديك
لكن PDB ومعلومات المستودع موجودان
يذهب أداة التصحيح لجلب مصدر الـ commit الصحيح

هذا يُحسِّن كثيراً تجربة مستخدمي المكتبة.

نقطة Source Link المهمّة هي أنّه يُشير إلى «الـ commit الذي أُنشئ منه ذلك الملفّ الثنائيّ»، وليس إلى «آخر فرع main».

المعنى يكمن في الارتباط بمصدر لحظة البناء، لا بأحدث مصدر.

منذ SDK الخاصّ بـ .NET 8 فصاعداً، تحسَّن التعامل مع Source Link.

في مزوّدين (providers) شائعين مثل GitHub وAzure Repos وGitLab وBitbucket، أصبحت آليّة Source Link مُضمَّنة في جانب .NET SDK نفسه.

لذا، أصبح الفهم القديم القائل بضرورة إضافة Microsoft.SourceLink.GitHub وما شابه صراحةً دائماً أمراً يتقادم.

لكن، توجد حالات يلزم فيها التحقّق.

البناء بـ SDK أقلّ من .NET 8
مشروع قديم ليس بنمط SDK
استخدام استضافة Git خاصّة داخل الشركة (on-premises)
استخدام مزوّد Source Link غير مدعوم قياسيّاً
الرغبة في تجهيز البيانات الوصفيّة الخاصّة بحزمة NuGet كذلك

إن أردت إظهار معلومات المستودع في حزمة NuGet، استخدم هذا الإعداد.

<PropertyGroup>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>

وإن احتجتَ إلى تضمين الملفّات غير المُتتبَّعة (untracked) داخل PDB، انظر في هذا الإعداد.

<PropertyGroup>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>

لكن، يؤثِّر التضمين على نطاق إفشاء المعلومات. يجب تحديد ما يُدرَج في PDB وفقاً لجهة النشر وسياسة التشغيل.

22. ما هو embedded PDB

عند تحديد embedded في DebugType، تُضمَّن معلومات تصحيح Portable PDB داخل .dll أو .exe. في هذه الحالة، لا يُنشأ ملفّ .pdb منفصل.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

هذا مفيد في مواقف من هذا القبيل.

  • الرغبة في التوزيع بشكل قريب من ملفّ واحد
  • تجنّب نسيان وضع PDB في مكانه
  • أداة داخليّة صغيرة تريد حمل معلومات التصحيح معها أيضاً
  • تقليل عبء توزيع PDB في حزمة NuGet

في المقابل، توجد عيوب أيضاً.

  • ازدياد حجم التجميعة (assembly)
  • تضمين معلومات التصحيح دائماً في المُنتَج الموزَّع
  • ضعف التحكّم في نطاق إفشاء المعلومات
  • تأثيره على restore وحجم التوزيع في المكتبات الكبيرة

embedded PDB مفيد، لكن ليس معناه «اجعل كلّ شيء embedded على أيّ حال، وكفى».

خاصّة في المكتبات المنشورة للخارج، يلزم التفكير في أيّها الأنسب: حزمة رموز (symbol package) عبر .snupkg، أم Source Link، أم إرفاق PDB عاديّ، أم embedded.

23. ما هو .snupkg

.snupkg صيغة حزمة رموز خاصّة بـ NuGet.

الحزمة العاديّة الخاصّة بـ NuGet هي .nupkg. وحزمة الرموز هي .snupkg.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

يحتوي .nupkg على جسم المكتبة الذي يرجع إليه المستخدم. ويُستخدَم .snupkg لتوزيع PDB الخاصّ بالتصحيح.

والمهمّ هنا هو أنّ .snupkg مُوجَّه أساساً لـ Portable PDB الخاصّ بالشيفرة المُدارة (managed code). فعلى الأقلّ خادم الرموز الخاصّ بـ NuGet.org لا يدعم إلّا Portable PDB، ولا يقبل Windows PDB الذي تنشئه مشاريع native مثل C++. إن أردتَ توزيع Windows PDB أو حفظه، فانظر في مسارات أخرى مثل .symbols.nupkg القديم، أو خادم الرموز الداخليّ للشركة، أو مُنتَجات CI.

لإنشائه، تكتب مثلاً هكذا.

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

يمكن تحديده أيضاً عبر سطر الأوامر.

dotnet pack -c Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg

في مكتبة NuGet عامّة، يسهُل الموازنة بين حجم التوزيع وتجربة التصحيح باستخدام .snupkg وSource Link معاً، بدل وضع PDB داخل .nupkg نفسه.

لكن انتبه إلى حالة دعم المصدر (feed) والأداة. إن كان مصدر NuGet الداخليّ للشركة لا يدعم .snupkg، يلزم اختيار طريقة أخرى.

24. هل ينبغي وضع PDB في بيئة الإنتاج

«هل ينبغي وضع PDB في بيئة الإنتاج» ليست مسألة نعم / لا بسيطة.

توجد أربعة محاور للحكم.

سهولة تحقيق الأعطال
مخاطر إفشاء المعلومات
حجم التوزيع
قواعد التشغيل

في نظام داخليّ، تشغيل يضع .dll و.pdb في نفس المجلّد أمر واقعيّ أيضاً. يسهُل ظهور رقم السطر في سجلّ الاستثناء، ويسهُل تحليل الملفّات التفريغيّة كذلك.

في تطبيق موزَّع للخارج، يُحسَم بحذر ما إذا كان يُرفَق PDB كما هو. قد تظهر البنية الداخليّة، وأسماء المتغيّرات المحليّة، ومسار المصدر. عند الحاجة، تُتَّخذ خيارات مثل الاقتصار على public symbols، أو التوزيع عبر خادم الرموز، أو حفظه للدعم فقط.

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

التوصية في العمل الفعليّ هي كالتالي.

يُنشأ PDB دائماً
يُحفَظ PDB كمُنتَج بناء
يُحسَم الوضع في بيئة الإنتاج وفق نطاق نشر النظام
عند النشر الخارجيّ، يُتحقَّق من نطاق إفشاء المعلومات
يُوضَع في مكان يمكن استخراجه منه عند تحليل الملفّ التفريغيّ

25. هل PDB معلومة سرّيّة

لا يلزم بالضرورة معاملة PDB معاملة الشيفرة المصدريّة نفسها أو المفتاح السرّيّ (secret key). لكن معاملته كملفّ غير ضارّ أمرٌ خطر أيضاً.

نذكر ما يمكن معرفته من PDB.

  • المسار المحلّي لدى المطوّر
  • بنية المجلّدات الداخليّة للشركة
  • اسم المشروع
  • أسماء الأصناف (classes) والدوال العضويّة
  • أسماء المتغيّرات المحليّة
  • أسماء الـ API الداخليّة
  • مصطلحات العمل
  • عنوان مستودع Source Link
  • المصدر المضمَّن
  • إعدادات Source Server

خاصّة في أسلوب Source Server القديم أو بعض وظائف Windows PDB الأصليّة، قد تدخل في الصورة آليّة تنفِّذ فيها أداة التصحيح أمراً (command) لجلب المصدر. ينبغي تجنّب استخدام PDB أو خادم رموز غير موثوق بشكل غير مشروط.

كذلك، يلزم الحذر عند إدخال PDB مُخرَّب في أداة أو مكتبة تُحلِّل PDB. في الآليّات التي تُعالِج تلقائيّاً PDB مُستلَماً من الخارج، ينبغي تصميم عدم الثقة في المُدخَل.

خلاصة القول، من الآمن معاملة PDB على هذا النحو.

يُحمى PDB الكامل الداخليّ للشركة كمُنتَج داخليّ
يُتحقَّق من محتوى ونطاق النشر عند نشر PDB خارجيّاً
إن كانت وجهة Source Link مستودعاً خاصّاً، تُدار المصادقة والصلاحيّات
لا يُسجَّل خادم رموز غير موثوق في أداة التصحيح

26. سياسة حفظ PDB في CI/CD

لا معنى لبقاء PDB في جهاز المطوّر المحلّي فقط. ما يلزم عند وقوع عطل في الإنتاج هو PDB المقابل للبناء الذي نُشر فعلاً.

لذا يُحفَظ في CI/CD على هذا النحو.

رقم البناء: 2026.06.10.1234
معرّف الـ commit: abcdef123456...
المُنتَجات:
  MyApp.dll
  MyApp.pdb
  MyApp.deps.json
  MyApp.runtimeconfig.json
  package.zip
  container image digest

كذلك، يُستحسَن ربط هذه المعلومات أيضاً قدر الإمكان.

Git commit
Git tag
رقم الإصدار
اسم البيئة
إعداد البناء
منصّة الاستهداف (target framework)
RID
container image digest
NuGet lock file

المهمّ ليس حفظ PDB بمفرده، بل عدم فقدان معرفة أيّ ملفّ ثنائيّ يقابله PDB.

استمرار وضع MyApp.pdb وحده في مشاركة ملفّات ينهار يوماً ما. اجعله قابلاً للاستخراج بوحدة البناء، ووحدة الإصدار، ووحدة الـ commit.

27. أنماط وضع PDB

توجد عدّة أنماط عمليّة للتعامل مع PDB.

النمط 1: وضع PDB في نفس مجلّد DLL

الأبسط.

publish/
  MyApp.dll
  MyApp.pdb

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

العيب هو تضمين معلومات التصحيح في المُنتَج الموزَّع. قد لا يناسب التوزيع الخارجيّ.

النمط 2: عدم وضع PDB، وحفظه في مُنتَجات CI

لا يُوضَع PDB في خادم الإنتاج، بل يبقى في مُنتَج (artifact) CI.

release-artifacts/
  app.zip
  symbols.zip

عند عطل إنتاج، تُلتَقَط الملفّات التفريغيّة والسجلّات، ويُستخرَج PDB المقابل لرقم البناء ثمّ يُحلَّل.

يسهُل تقليص نطاق إفشاء المعلومات، لكن يلزم إجراء استخراج عند التحقيق.

النمط 3: النشر إلى خادم الرموز الداخليّ للشركة

هذا مناسب للفرق الكبيرة.

يُصدر CI ملفّ PDB إلى مستودع الرموز لحظة البناء. ويرجع أداة التصحيح لدى المطوّرين أو المحقِّقين، سواء Visual Studio أو WinDbg، إلى ذلك خادم الرموز.

الميزة هي سهولة التعامل الآمن مع إصدارات متعدّدة من PDB. العيب هو الحاجة إلى إعداد أوّليّ وضبط التحكّم في الوصول.

النمط 4: التوزيع عبر .snupkg الخاصّ بـ NuGet

خيار قويّ في المكتبات العامّة.

MyLibrary.1.2.3.nupkg
MyLibrary.1.2.3.snupkg

يستعيد المستخدم الحزمة العاديّة فقط، ولا تُجلَب سوى الرموز اللازمة وقت التصحيح.

عند الجمع مع Source Link، يسهُل الدخول خطوة بخطوة إلى المصدر حتّى في المكتبات الخارجيّة.

النمط 5: جعله embedded PDB

مفيد لتجنّب نسيان وضع PDB في مكانه.

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

لكن انتبه إلى حجم التجميعة ونطاق إفشاء المعلومات.

28. ما ينبغي التحقّق منه أوّلاً في مشروع قائم

عند إعادة النظر في تعامل مشروع .NET قائم مع PDB، تحقَّق أوّلاً من هذه النقاط.

هل يُنشأ PDB في بناء Release
أين يُحفَظ PDB المُنشأ
هل يُحفَظ DLL وPDB الصادران للإنتاج مرتبطَين ببعضهما
هل يمكن استخراج PDB عند تحليل الملفّ التفريغيّ
هل Source Link مفعَّل
إن كانت مكتبة NuGet، هل يُخرَج `.snupkg`
هل يحتوي PDB معلومات لا داعي لها بشكل مُفرط

في csproj، تحقَّق من هذه الإعدادات تقريباً.

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

إن كانت حزمة NuGet، فهذا مرشَّح أيضاً.

<PropertyGroup>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

لكن، ليس صحيحاً أن يوضَع نفس الإعداد في كلّ المشاريع. يختلف الحلّ الأمثل باختلاف كون المشروع تطبيقاً داخليّاً، أو مكتبة خارجيّة، أو مُنتَجاً محلّيّاً (on-premises)، أو SaaS، أو مشروع OSS.

29. كيفيّة النظر عندما لا يُحمَّل PDB

عندما لا يُحمَّل PDB، لا تُعِد البناء بالحدس، بل افصل الاحتمالات بالترتيب.

1. تحقّق من الوحدة (module) المستهدَفة

في نافذة Modules الخاصّة بـ Visual Studio، ابحث عن DLL / EXE المستهدَف.

Debug > Windows > Modules

الأعمدة التي ينبغي النظر إليها هنا هي هذه.

Module
Path
Symbol Status
Symbol File
Version
Timestamp

2. انظر حالة الرموز

يُقرَأ كلّ معروض تقريباً كالآتي.

المعروض المعنى
Symbols loaded تمّ التحميل
Cannot find or open the PDB file لم يُعثَر على PDB
PDB does not match image PDB موجود لكن لا يطابق الملفّ الثنائيّ المستهدَف
Skipped loading symbols يُحتمَل عدم التحميل بسبب الإعداد

3. تحقّق من كون PDB الموجود لديك من نفس البناء

الخطأ الشائع هو استخدام PDB أعدتَ بناءه محليّاً.

حتّى لو كان نفس المصدر، قد لا يتطابق إن اختلفت شروط البناء. احصل من مُنتَج CI على نفس PDB الذي صدر للإنتاج.

4. تحقّق من مسار الرموز

في Visual Studio، تحقَّق من هنا.

Tools > Options > Debugging > Symbols

في WinDbg، تحقَّق مثلاً هكذا.

.sympath
.reload
!sym noisy

عند استخدام رموز Microsoft العامّة، من المفيد تحديد ذاكرة تخزين مؤقّتة محلّيّة أيضاً.

srv*C:\Symbols*https://msdl.microsoft.com/download/symbols

5. اشتبه في الذاكرة المؤقّتة (cache)

قد تكون قد أمسكت PDB قديماً أو ذاكرة مؤقّتة تالفة. تحقَّق عبر حذف ذاكرة تخزين الرموز المؤقّتة، أو تحديد ذاكرة مؤقّتة أخرى، أو النظر بتفصيل أكبر في سجلّ التحميل.

30. PDB و«Just My Code»

في Visual Studio ميزة تُسمَّى Just My Code.

هي ميزة تحصر هدف التصحيح في «الشيفرة الخاصّة بك»، وتجعل التنفيذ خطوة بخطوة داخل الشيفرة الخارجيّة صعباً. مفيدة في التطوير الاعتياديّ، لكنّها قد تكون سبباً للارتباك عند التحقّق من PDB أو Source Link.

على سبيل المثال، إذا تعذّر الدخول خطوة بخطوة رغم وجود PDB وSource Link في مكتبة NuGet خارجيّة، تحقَّق من هذه النقاط.

هل Just My Code مفعَّل ويُعامَل كشيفرة خارجيّة
هل Enable Source Link support مفعَّل
هل NuGet.org Symbol Server مفعَّل
هل الحزمة المستهدَفة تنشر PDB / .snupkg
هل يمكن الوصول إلى وجهة جلب المصدر

قبل الحكم بأنّ «PDB هو المشكلة»، تحقَّق أيضاً من إعدادات أداة التصحيح.

31. PDB والتفكيك (decompile)

يستطيع Visual Studio الحديث تفكيك (decompile) تجميعات .NET واستخدامها في التصحيح.

بهذا، حتّى المكتبات الخارجيّة التي لا يوجد لها PDB أو مصدر، يمكن تتبّع محتواها إلى حدّ ما.

لكن التفكيك ليس شاملاً.

لا تعود التعليقات الأصليّة
لا تعود المسافات البيضاء أو البنية الأصليّة
قد تتغيّر أسماء المتغيّرات المحليّة
قد تظهر async / iterator / pattern matching وغيرها بشكل مختلف عن الشكل الأصليّ
يصعب فهم التطابق في الشيفرة المُحسَّنة (optimized)

إن وُجد PDB وSource Link، فاستخدامهما هو الأصوب أساساً. يُستحسَن اعتبار التفكيك «وسيلة مساعدة عند عدم وجود PDB أو مصدر».

32. تصميم السجلّات (logging) وPDB

يتعلّق PDB أيضاً بتصميم السجلّات.

على سبيل المثال، عند ظهور اسم الملفّ ورقم السطر في سجلّ الاستثناء، يسهُل التحقيق. لكنّ الاعتماد على السجلّ وحده خطر.

في أعطال الإنتاج، يحدث أمر من هذا القبيل.

رقم السطر الظاهر في السجلّ لا يتطابق مع فرع main الحاليّ
أُعيد النشر بعد hotfix بنفس رقم الإصدار
لم يبقَ PDB، فلا يمكن التحقّق من معنى رقم السطر
تبقى صورة الحاوية (container image) لكن لا يُعرَف الـ commit المصدريّ المقابل

لذا، من الجيّد إخراج معلومات البناء في السجلّ، لا رقم السطر وحده.

ApplicationVersion: 1.8.3
GitCommit: abcdef1234567890
BuildNumber: 20260610.12
Environment: Production

عندما يترابط PDB والمصدر والسجلّ والملفّ التفريغيّ وتاريخ النشر، يصبح تحقيق الأعطال أسهل بكثير.

33. PDB في تشغيل الحاويات (containers)

عند تشغيل تطبيق .NET في حاوية، يلزم توضيح كيفيّة التعامل مع PDB.

على سبيل المثال، هل يُدرَج PDB في صورة Docker أم لا.

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "MyApp.dll"]

إن وُجد PDB في publish/، يدخل في الصورة كما هو.

لهذا مزايا.

  • يسهُل ظهور رقم السطر في تتبّع المكدّس داخل الحاوية
  • يسهُل العثور عليه عند التقاط ملفّ تفريغيّ على نفس نظام الملفّات
  • سهولة التعامل عند التحقيق

في المقابل، توجد مخاوف أيضاً.

  • ازدياد حجم الصورة
  • تضمين معلومات التصحيح في صورة الإنتاج
  • عند توزيع الصورة خارجيّاً، يتّسع نطاق إفشاء المعلومات

في SaaS داخليّ خاصّ، خيار تضمين PDB في الصورة وارد بشكل كافٍ. أمّا في مُنتَج محلّي (on-premises) يُسلَّم للعميل الخارجيّ، فمن الأفضل أحياناً حفظ PDB بشكل منفصل.

على أيّ حال، حتّى إن لم يُدرَج PDB في الصورة، يبقى حفظ PDB المقابل كمُنتَج (artifact) إلزاميّاً.

34. النشر بملفّ واحد وPDB

يملك .NET نشراً بملفّ واحد (single file).

dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true

في هذه الحالة أيضاً، يلزم التحقّق من كيفيّة التعامل مع معلومات التصحيح.

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

الأمور التي تُحسَم كسياسة هي تقريباً هذه.

هل يُوزَّع PDB كملفّ منفصل
هل يُضبَط DebugType=embedded
هل تُحفَظ الرموز داخليّاً فقط
كيف تُحلَّل الملفّات التفريغيّة عند الانهيار

عند استخدام النشر بملفّ واحد، والتقليم (trimming)، وAOT وما شابه، قد تختلف تجربة التحقيق عن التجميعة (assembly) العاديّة بصيغة IL. من الآمن تجربة «كيف تُقرَأ عند حدوث انهيار» قبل الإصدار مرّة واحدة.

35. PDB والتقليم (trimming) / AOT

في .NET الحاليّ، يُستخدَم أحياناً التقليم أو Native AOT.

في هذه الحالة، لا يتعلّق الأمر بـ PDB وحده، بل أيضاً برموز native المُنشأة ومعلومات التصحيح الخاصّة بكلّ منصّة.

على سبيل المثال، DWARF في Linux، وdSYM في macOS، وPDB في Windows - يلزم التفكير في معلومات التصحيح الخاصّة بالجانب native أيضاً.

حتّى في تطبيقات .NET، يصبح تصميم الرموز معقّداً في مثل هذه التركيبات.

Native AOT
النشر Self-contained
PublishSingleFile
ReadyToRun
استدعاء DLL أصليّ عبر P/Invoke
تضمين C++/CLI

في تطبيق ويب أو مكتبة صنف اعتياديّة، يكفي فهم Portable PDB وSource Link أوّلاً. لكن كلّما ازداد تقدّم صيغة التوزيع، يلزم إدراج «كيف يُحلَّل هذا الانهيار» ضمن تصميم البناء.

36. نقاط الحذر في مشاريع .NET Framework

في مشاريع .NET Framework القديمة، قد تختلف الإعدادات والقيم الافتراضيّة عن مشاريع نمط SDK.

على سبيل المثال، فوارق من هذا القبيل.

صيغة csproj قديمة
استخدام packages.config
القيمة الافتراضيّة لـ DebugType تختلف عن .NET الحاليّ
استخدام Windows PDB
يلزم حزم إضافيّة أو إعدادات MSBuild خاصّة لـ Source Link
إصدار MSBuild الخاصّ بـ CI قديم

فكرة PDB نفسها لا تتغيّر في .NET Framework أيضاً.

غير ضروريّ للتشغيل
مهمّ للتصحيح وتحقيق الأعطال
يلزم PDB مطابق للملفّ الثنائيّ المستهدَف
يجب حفظ PDB الخاصّ ببناء Release

لكن، إذا نُسخ شرح .NET الحاليّ كما هو، فقد لا يعمل كما هو متوقَّع في المشاريع القديمة.

في الأصول القائمة، تحقَّق أوّلاً من الناتج الفعليّ.

msbuild MyApp.csproj /p:Configuration=Release
Get-ChildItem bin\Release -Filter *.pdb -Recurse

ثمّ رتِّب إعدادات البناء ومُنتَجات CI بناءً على ذلك.

37. كيف يُتعامَل في مكتبات OSS

في مكتبة .NET مفتوحة المصدر (OSS)، يُنصَح أساساً بهذه البنية.

يُنشأ PDB حتّى في Release
استخدام Portable PDB
تفعيل Source Link
نشر .snupkg على NuGet
ضبط بيانات Repository الوصفيّة
مراعاة البناء الحتميّ (deterministic build)

مثال.

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

باختلاف SDK وجهة الاستضافة، قد لا تحتاج أحياناً إلى حزمة Source Link إضافيّة. في SDK قديم أو استضافة خاصّة، أضِف حزمة Microsoft.SourceLink.* المقابلة.

في OSS، إمكانيّة دخول المستخدم خطوة بخطوة داخل المكتبة تصبح جودة بحدّ ذاتها. «المكتبة التي يمكن قراءتها عند وقوع مشكلة» تكتسب ثقة أكبر بذلك وحده.

38. كيف يُتعامَل في مكتبات داخليّة للشركة

في المكتبات الداخليّة أيضاً، Source Link وPDB فعّالان.

بل إنّ المكتبات الداخليّة تحديداً تكون مفيدة إذا أمكن الدخول إليها خطوة بخطوة من التطبيق التجاريّ.

إن كنتَ تستخدم مصدر NuGet داخليّاً، انظر في هذه النقاط.

هل مصدر الشركة الداخليّ يدعم .snupkg
إن لم يكن يدعم، هل يُدرَج PDB داخل .nupkg
هل يُجهَّز خادم رموز داخليّ
كيف تُدار المصادقة إلى مستودع Git
هل يمكن الوصول إلى المصدر حتّى بعد استقالة موظّف أو انتقاله

لا ينبغي تجنّب حالة بقاء PDB في جهاز محلّي فقط لمجرّد كونه للاستخدام الداخليّ.

يُنشَأ عبر CI ويُحفَظ في مكان يمكن للفريق استخراجه منه.

39. كيف يُتعامَل في المُنتَجات المحلّيّة (on-premises)

في المُنتَجات المحلّيّة الموزَّعة إلى بيئة العميل، يصبح التعامل مع PDB صعباً.

إن أُرفِق PDB، يسهُل قراءة الملفّات التفريغيّة والسجلّات المُلتقَطة لدى العميل. لكنّ البنية الداخليّة تصبح أكثر ظهوراً.

الخيارات الشائعة تقريباً هذه.

عدم إرفاق PDB، مع حفظ نسخة كاملة لدى المُورِّد
تقديم public symbols فقط لأغراض دعم العميل
جمع الملفّات التفريغيّة عند العطل وتحليلها باستخدام PDB لدى المُورِّد
تقديم حزمة رموز محدودة لعملاء مهمّين جدّاً

المهمّ هو عدم فقدان PDB بعد الإصدار.

في المُنتَجات المحلّيّة، قد يُطلَب أحياناً تحقيق عطل إصدار يعود لسنوات مضت. في تلك اللحظة، إن لم يوجد PDB المقابل، تنخفض القدرة على التحقيق كثيراً.

40. ماذا لو حذفتَ PDB

حتّى لو حذفتَ PDB، يستمرّ التطبيق في العمل. لكن تقع في مأزق لاحقاً.

قد تظنّ أنّه يمكن إعادة إنتاجه بإعادة البناء من نفس الـ commit. لكنّ إعادة الإنتاج الكاملة صعبة بشكل غير متوقَّع.

اختلاف إصدار SDK
اختلاف نتيجة حلّ NuGet
اختلاف وقت البناء أو متغيّرات البيئة
اختلاف الشيفرة المُولَّدة
اختلاف الترجمة الشرطيّة
وجود إعداد يُضاف فقط في CI
اختلاف أداة native يُعتمَد عليها

إن كان البناء الحتميّ (deterministic build) مُجهَّزاً، تزداد قابليّة إعادة الإنتاج، لكن يبقى «حفظه كمُنتَج منذ البداية» أكثر أماناً.

PDB أقرب إلى بوليصة تأمين. لا تظهر قيمته إلّا عند الحاجة إليه. وإن لم يوجد وقت الحاجة إليه، لا يمكن تدارك الأمر.

41. الإعداد المُوصى به في العمل الفعليّ

لا يوجد إعداد واحد وحيد ينطبق على كلّ مشروع. لكن، في تطبيق .NET عامّ، يمكن اتّخاذ هذا كمرجعيّة أوّليّة.

التطبيق الداخليّ

<PropertyGroup>
  <DebugType>portable</DebugType>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

السياسة كالتالي.

يُنشأ PDB حتّى في Release
يُحسَم وضعه في الإنتاج وفق سياسة التشغيل
يُحفَظ دائماً في مُنتَج CI
يُجهَّز إجراء تحليل الملفّات التفريغيّة

مكتبة NuGet عامّة

<PropertyGroup>
  <DebugType>portable</DebugType>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

السياسة كالتالي.

تفعيل Source Link
نشر .snupkg
تجنّب تضمين مصدر لا داعي له
التحقّق من البيانات الوصفيّة المنشورة

أداة داخليّة صغيرة

<PropertyGroup>
  <DebugType>embedded</DebugType>
</PropertyGroup>

السياسة.

تجنّب نسيان وضع PDB في مكانه
قبول ازدياد حجم المُنتَج الموزَّع
الاقتصار على الاستخدام الداخليّ

مُنتَج موزَّع للخارج

تُحفَظ نسخة كاملة من PDB داخليّاً
تُصنَع public symbols بشكل منفصل عند الحاجة
يُتحقَّق من محتوى PDB المُدرَج في المُنتَج الموزَّع للعميل
تُحدَّد إجراءات جمع الملفّات التفريغيّة وتحليلها وقت الدعم

42. قائمة تحقّق عند النظر إلى PDB

أخيراً، نضع قائمة تحقّق للرجوع إليها عند التردّد في التعامل مع PDB.

لأيّ DLL / EXE يقابل هذا PDB
من أيّ commit بُني ذلك الـ DLL / EXE
هل هو PDB الخاصّ ببناء Release الفعليّ، لا لبناء Debug
هل PDB محفوظ كمُنتَج CI
هل يوجد خادم رموز أو إجراء استخراج
هل Source Link مفعَّل
هل صلاحيّة جلب المصدر مناسبة
هل يحتوي PDB معلومات لا تريد نشرها
إن كان توزيعاً خارجيّاً، هل توجد سياسة لـ public/private symbol
هل تحقّقتَ من إمكانيّة قراءة PDB عند تحليل الملفّ التفريغيّ

المهمّ بوجه خاصّ هذه الثلاث.

PDB ليس ملفّاً تنفيذيّاً، بل معلومة موجَّهة للتحقيق
يجب أن يتطابق PDB مع الملفّ الثنائيّ المستهدَف
يجب حفظ PDB قبل وقوع عطل الإنتاج

43. الخلاصة

PDB ليس «ملفّاً غير مفهوم» يظهر بجانب .dll أو .exe، بل هو معلومة تصحيح تربط بين الملفّ الثنائيّ بعد البناء والشيفرة المصدريّة التي يقرؤها المطوّر.

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

PDB مفيد أيضاً في بناء Release. بل إنّ ما يلزم في عطل الإنتاج هو تحديداً PDB المقابل لبناء Release.

يُحسَم وضع PDB في بيئة الإنتاج وفق نطاق إفشاء المعلومات وسياسة التشغيل. لكن إنشاء PDB وحفظه كمُنتَج بناء أمرٌ لازم في معظم المشاريع.

في العمل الفعليّ، من الجيّد اتّخاذ هذه السياسة أساساً.

يُنشأ PDB حتّى في Release
يُحفَظ PDB كمُنتَج CI/CD
يُربَط الملفّ الثنائيّ وPDB ومعرّف الـ commit ورقم البناء ببعضهما
يُجعَل قابلاً للوصول إلى المصدر عبر Source Link
يُنظَر في .snupkg بالنسبة لمكتبات NuGet
يُتحقَّق من معلومات الرموز المنشورة في التوزيع الخارجيّ

لا يبرز PDB حين لا توجد مشكلة. لكنّه دليل مهمّ يُعيد المحقِّق إلى الشيفرة المصدريّة حين تقع المشكلة.

بدل «PDB يُحذَف ويستمرّ العمل»، من الأفضل التفكير هكذا:

PDB هو الخريطة اللازمة لتحقيق أعطال المستقبل.

المراجع

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

ما هو ملفّ PDB؟
PDB اختصار لـ Program Database (قاعدة بيانات البرنامج)، ويُسمَّى أيضاً ملفّ الرموز (symbol file)، وهو ملفّ معلومات تصحيح. يقوم بدور الربط بين `.dll` أو `.exe` بعد البناء (build) وبين الشيفرة المصدريّة (source code) التي يمكن للمطوّر قراءتها. يحتوي على أسماء الدوال والدوال العضويّة (methods)، وأسماء المتغيّرات المحليّة، واسم ملفّ المصدر ورقم السطر، والتطابق بين الموضع في المصدر والتعليمة (instruction) بعد الترجمة، ومعلومات جلب المصدر الخاصّة بـ Source Link، وتستخدمها أدوات التصحيح والتشخيص للحكم على «أيّ سطر مصدريّ تقابله هذه التعليمة».
هل يعمل التطبيق دون ملفّ PDB؟ وهل يجوز حذفه؟
عادةً لا يكون PDB ضروريّاً لتشغيل التطبيق، ويكفي وجود `.dll` أو `.exe` للتشغيل. لكن دون PDB، يصعب استخدام نقاط التوقّف (breakpoints) والتنفيذ خطوة بخطوة (step execution)، وعرض اسم الملفّ ورقم السطر في تتبّع مكدّس الاستثناء (exception stack trace)، وتحليل الملفّات التفريغيّة (dump)، والدخول خطوة بخطوة (step into) إلى المكتبات الخارجيّة. فـ PDB ليس «ملفّاً للتشغيل» بل «ملفّاً للتحقيق»، لذا بصرف النظر عن وضعه في بيئة الإنتاج من عدمه، يجب حفظه دائماً كمُنتَج بناء (build artifact). فإن حُذف، يصعب أحياناً - رغم إعادة البناء من نفس الـ commit - تحقيق إعادة إنتاج كاملة، ما قد يجعل الأمر لا يُعوَّض عند تحقيق عطل.
هل يلزم PDB في بناء Release أيضاً؟
نعم، بل إنّ ما يلزم عند تحقيق أعطال الإنتاج هو تحديداً PDB الخاصّ ببناء Release. فإذا كان ما يعمل في الإنتاج هو بناء Release، فامتلاك PDB الخاصّ ببناء Debug عديم الجدوى، وما يحتاجه أداة التصحيح (debugger) هو PDB الذي أُنشئ لحظة بناء ذلك الملفّ الثنائيّ (binary) الفعليّ الخاصّ بالإنتاج. ولأنّ PDB يجب أن يتطابق مع الملفّ الثنائيّ المستهدَف، فالأساس في CI/CD هو حفظ معرّف الـ commit ورقم البناء و`.dll`/`.exe` و`.pdb` معاً كمجموعة واحدة. ومع ذلك، في مشاريع .NET SDK الحاليّة النمط، يُنتَج Portable PDB افتراضيّاً في كلّ من Debug وRelease.
هل يجوز وضع ملفّ PDB في بيئة الإنتاج؟
ليس الأمر بـ«نعم أو لا» بسيطاً، بل يُحكَم عليه وفق أربعة محاور: سهولة تحقيق الأعطال، ومخاطر إفشاء المعلومات، وحجم التوزيع، وقواعد التشغيل. في نظام داخليّ، وضع `.dll` و`.pdb` في نفس المجلّد أمر واقعيّ، إذ يسهِّل ظهور رقم السطر في سجلّات الاستثناء. أمّا في التوزيع الخارجيّ، فقد يُستدلّ من PDB على المسار المحلّي، وبنية المجلّدات الداخليّة، وأسماء الأنواع والمتغيّرات المحليّة، وعنوان مستودع Source Link، لذا ينبغي الحسم بحذر، واختيار الاقتصار على public symbols عند الحاجة، أو التوزيع عبر خادم الرموز (symbol server)، أو غير ذلك من الخيارات. وحتّى لو لم يُوضَع، يبقى حفظ PDB الخاصّ بنفس البناء أمراً إلزاميّاً.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

روابط عامة

العودة إلى المدونة