عندما تستخدم GDB ، يمكنك أن ترى أن لديها سيطرة كاملة على عملية التطبيق الخاصة بك. اضغط على Ctrl C أثناء تشغيل التطبيق وتوقف تنفيذ العملية ، ويظهر GDB موقعه الحالي ، تتبع المكدس ، وما إلى ذلك

ولكن كيف يمكن أن تفعل ذلك؟

كيف لا تعمل؟

لنبدأ أولاً بكيفية عدم عمل . لا يحاكي التنفيذ ، من خلال قراءة وتفسير التعليمات الثنائية. يمكن أن يعمل ، وهذا من شأنه أن يعمل (بهذه الطريقة Valgrind يعمل مصحح أخطاء الذاكرة) ، لكن هذا سيكون بطيئًا جدًا . يقوم Valgrind بإبطاء التطبيق بمقدار 1000 مرة ، بينما لا يقوم GDB بذلك. هذه أيضًا طريقة عمل الأجهزة الافتراضية مثل Qemu .

إذن ، ما الحيلة؟ السحر الأسود! … لا ، سيكون ذلك سهلاً للغاية.

تخمين آخر؟ …؟ القرصنة! نعم ، هناك قدر كبير من ذلك ، بالإضافة إلى المساعدة من نواة نظام التشغيل.

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

Linux Ptrace API

Linux ptrace API يسمح لعملية (مصحح الأخطاء) بالوصول إلى معلومات منخفضة المستوى حول عملية أخرى (المصحح). على وجه الخصوص ، يمكن لمصحح الأخطاء:

  • قراءة وكتابة ذاكرة المصحح: PTRACE_PEEKTEXT ، PTRACE_PEEKUSER ، PTRACE_POKE …

قراءة وكتابة سجلات وحدة المعالجة المركزية الخاصة بمصحح الأخطاء: PTRACE_GETREGSET ، PTRACE_SETREGS ،

  • يتم إخطارها بأحداث النظام: PTRACE_O_TRACEEXEC ، PTRACE_O_TRACECLONE ، PTRACE_O_EXITKILL ، PTRACE_SYSCALL (يمكنك التعرف على exec syscall ، استنساخ ، خروج ، وجميع نداءات النظام الأخرى)
  • تتحكم في تنفيذها: PTRACE_SINGLESTEP ، PTRACE_KILL ، PTRACE_INTERRUPT ، PTRACE_CONT (لاحظ خطوة واحدة لوحدة المعالجة المركزية هنا )

  • يغير معالجة الإشارات الخاصة به: PTRACE_GETSIGINFO ، PTRACE_SETSIGINFO
  • كيف يتم تنفيذ Ptrace؟

    تنفيذ Ptrace خارج نطاق هذا المنشور ، لكنني لا أريد نقل الصندوق الأسود خطوة واحدة أعلاه ، لذلك اسمحوا لي أن أشرح بسرعة كيف يعمل (لست خبيرًا في kernel ، من فضلك صححني إذا كنت مخطئًا واعذرني إذا قمت بالتبسيط أيضًا الكثير: -).

    Ptrace

    جزء من Linux kernel ، لذلك يمكنه الوصول إلى all معلومات مستوى kernel حول العملية:

    تغيير معالجة الإشارة؟ تحديث الحقل last_siginfo

  • خطوة واحدة ؟ ضع العلم الصحيح ( ARM ، x86 ) على هيكل المهمة وقبل بدء التنفيذ على المعالج.

  • Ptrace أيضًا مدمن مخدرات (ابحث عن وظيفة ptrace_event ) في العديد من عمليات الجدولة ، بحيث يمكن n إرسال
      إشارة SIGTRAP  
  • إلى مصحح الأخطاء عند الطلب ( PTRACE_O_TRACEEXEC الخيار وعائلته).

    وماذا عن الأنظمة بدون Ptrace؟

    التفسير أعلاه يستهدف تصحيح أخطاء Linux الأصلي ، ولكنه صالح لمعظم البيئات الأخرى. للحصول على فكرة عما يطلبه GDB من أهدافه المختلفة ، يمكنك إلقاء نظرة على عمليات مكدس الهدف .

    في هذه الواجهة المستهدفة ، يمكنك رؤية جميع العمليات عالية المستوى المطلوبة لتصحيح أخطاء C:

    Struct target_ops {Struct target_ops under؛ / إلى الهدف تحت هذا. / const char to_shortname ؛ / قم بتسمية نوع الهدف هذا / const char to_longname ؛ / اسم الطباعة / const char to_doc ؛ /توثيق. لا يتضمن سطرًا جديدًا متدرجًا ، ويبدأ بوصف من سطر واحد (ربما يكون مشابهًا لـ to_longname). / void to_attach) (architecture target_ops ops، const char *، int) ؛ void to_fetch_registers) (architecture target_ops *، architecture regcache *، int) ؛ void to_store_registers) (هيكل target_ops ، بنية regcache ، int) ؛ int to_insert_breakpoint) (الهيكل المستهدف ، الهيكل gdbarch ، الهيكل bp_target_info ؛ int to_insert_watchpoint) (هيكل target_ops ، CORE_ADDR ، int ، int ، تعبير هيكلي ؛ … }

    ال يستدعي الجزء العام من GDB هذه الوظائف ، وتقوم الأجزاء المحددة الهدف بتنفيذها. إنه (من الناحية المفاهيمية) على شكل مكدس أو هرم: الجزء العلوي من المكدس عام تمامًا ، على سبيل المثال:

    أسلوب التصحيح المحدد ( ptrace

  • ،
  • ttrace
  • ) مجموعة تعليمات محددة (Linux ذراع ، Linux x86 )

    ال الهدف البعيد مثير للاهتمام ، حيث يقسم كومة التنفيذ بين “جهازي كمبيوتر” ، من خلال بروتوكول اتصال (TCP / IP ، منفذ تسلسلي) .

    يمكن أن يكون الجزء البعيد gdbserver ، يعمل في مربع لينكس آخر. ولكن يمكن أن تكون أيضًا واجهة لمنفذ تصحيح أخطاء الأجهزة (JTAG) أو برنامج Hypervisor لجهاز افتراضي (على سبيل المثال Qemu ) ، والتي ستلعب دور النواة + ptrace. بدلاً من الاستعلام عن بنى kernel لنظام التشغيل ، فإن كعب مصحح الأخطاء البعيد سوف يستعلم عن هياكل برنامج Hypervisor ، أو مباشرة في سجلات الأجهزة الخاصة بالمعالج.

    لمزيد من القراءة حول هذا البروتوكول البعيد ، كتب Embecosm دليل تفصيلي حول الرسائل المختلفة .

      Gdbserver حلقة معالجة الحدث موجودة ، و Qemu gdb-server stub متصل أيضًا.

      لنلخص

      يمكننا أن نرى هنا أن جميع الآليات ذات المستوى المنخفض المطلوبة لتنفيذ مصحح الأخطاء موجودة ، والتي يوفرها هذا ptrace API:

    • قبض على exec syscall وحظر بدء التنفيذ ،

    استعلم عن سجلات وحدة المعالجة المركزية للحصول على التعليمات الحالية للعملية وموقع المكدس ، اصطياد استنساخ / شوكة أحداث لاكتشاف المواضيع الجديدة

  • إلقاء نظرة خاطفة على عناوين البيانات لقراءة متغيرات الذاكرة وتغييرها.
  • ولكن هل هذا كل ما يفعله مصحح الأخطاء؟ لا ، هذا فقط الأجزاء ذات المستوى المنخفض جدًا … كما أنه يتعامل مع التعامل مع الرموز. هذا هو الرابط بين الكود الثنائي ومصادر البرنامج. ولا يزال هناك شيء واحد مفقود ، ربما الأهم: نقاط التوقف! سأشرح أولاً كيفية عمل نقاط التوقف لأنها مثيرة للاهتمام وصعبة للغاية ، ثم سأعود إلى إدارة الرموز.

    نقاط التوقف ليست جزءًا من Ptrace API

    كما رأينا أعلاه ، فإن نقاط التوقف ليست جزءًا من ptrace خدمات API. لكن يمكننا تغيير الذاكرة ، واستقبال إشارات debugee. لا يمكنك رؤية الرابط؟ ذلك لأن تطبيق نقاط التوقف صعب للغاية ومخترق! دعونا نفحص كيفية تعيين نقطة توقف في عنوان معين:

    يقرأ مصحح الأخطاء (نظرة خاطفة) التعليمات الثنائية المخزنة في هذا العنوان ، ويحفظه في هياكل البيانات الخاصة به. يكتب تعليمة غير صالحة في هذا الموقع. مهما كانت هذه التعليمات ، يجب أن تكون غير صالحة.

    عندما يصل المصحح إلى هذه التعليمات غير الصالحة (أو بعبارة أكثر دقة ، المعالج ، الإعداد مع سياق ذاكرة تصحيح الأخطاء) ، لن يتمكن من تنفيذه (لأنه غير صالح). في أنظمة التشغيل الحديثة متعددة المهام ، لا تؤدي التعليمات غير الصالحة إلى تعطل النظام بأكمله ، ولكنها تمنح التحكم مرة أخرى نظام التشغيل kernel ، عن طريق رفع مقاطعة (أو خطأ). تمت ترجمة هذا الانقطاع بواسطة Linux إلى SIGTRAP ، وتنتقل إلى العملية … أو إلى والدها ، كما طلب المصحح.

  • يحصل مصحح الأخطاء على معلومات حول الإشارة ، ويتحقق من قيمة مؤشر تعليمات المصحح (على سبيل المثال ، حيث وقع الفخ). إذا كان عنوان IP موجودًا في قائمة نقاط التوقف الخاصة به ، فهذا يعني أنه نقطة توقف لمصحح الأخطاء (وإلا فإنه خطأ في العملية ، فقط قم بتمرير الإشارة واتركها تنهار).
  • الآن بعد أن تم إيقاف المصحح عند نقطة التوقف ، يمكن لمصحح الأخطاء السماح لمستخدمه بفعل ما يريد ، حتى يحين وقت متابعة التنفيذ.

  • للمتابعة ، يحتاج مصحح الأخطاء إلى 1 / إعادة كتابة التعليمات الصحيحة في ذاكرة المصحح ، 2 / خطوة واحدة (متابعة التنفيذ لواحد تعليمات وحدة المعالجة المركزية ، مع خطوة واحدة ptrace) و 3 / كتابة التعليمات غير الصالحة مرة أخرى (بحيث يمكن أن يتوقف التنفيذ مرة أخرى في المرة القادمة). و 4 / دع التنفيذ يسير بشكل طبيعي.
  • أنيق ، أليس كذلك؟ كملاحظة جانبية ، يمكنك ملاحظة أن هذه الخوارزمية لن تعمل إذا لم تعمل تم إيقاف جميع الخيوط في نفس الوقت (لأن تشغيل المواضيع قد يجتاز نقطة التوقف عندما تكون التعليمات صالحة في مكانها). لن أشرح بالتفصيل الطريقة التي حل بها رجال GDB ، لكن تمت مناقشة هذه الورقة بالتفصيل:

    تصحيح أخطاء متعدد الخيوط بدون توقف في GDB . ضع باختصار ، يكتبون التعليمات في مكان آخر في الذاكرة ، ويضبطون مؤشر التعليمات على هذا الموقع ويخطون المعالج بخطوة واحدة. لكن المشكلة أن بعض التعليمات مرتبطة بالعنوان ، على سبيل المثال القفزات والقفزات الشرطية …

    معالجة الرموز وتصحيح الأخطاء

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

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

    التمثيل=> 0x402c60: دفع٪ r15 0x402c62: دفع٪ r14 0x402c64: دفع٪ r13 0x402c66: دفع٪ r12 0x402c68: mov٪ rsi ،٪ r12 0x402c6b: push٪ rbp 0x402c6c: mov٪ edi،٪ ebp 0x402c6e: push2 rbx : sub $ 0x3a8 ،٪ rsp 0x402c76: mov (٪ rsi) ،٪ rdi

    الآن إذا أضفنا معلومات معالجة الرموز ، يمكن لـ GDB مطابقة العناوين بأسماء الرموز:

     (gdb) $ pc $ 1=(باطل  ()) 0x402c60   

    يمكنك سرد رموز ثنائي ELF باستخدام nm -a $ file :

    نانومتر -a / usr / lib /debug/usr/bin/ls.debug | grep "main" 0000000000402c60 T main

    سيكون GDB قادرًا أيضًا على عرض تتبع المكدس (المزيد عن ذلك لاحقًا) ، ولكن باهتمام محدود:

     (gdb) حيث # 0 اكتب () # 1 0x0000003d492769e3 في _IO_new_file_write () # 2 0x0000003d49277e4c في new_do_write () # 3 _IO_new_do_write () # 4 0x0000003d49278223 في _IO_new_file_overflow () # 5 0x00000000004085bb في print_current_files () # 6 0x000000000040431b في main ()  

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

    الآن دعنا نضيف معلومات التصحيح: هذا هو معيار DWARF ، خيار gcc -g . لست على دراية بهذا المعيار ، لكنني أعلم أنه يوفر:

    ستعتمد العديد من أوامر تصحيح الأخطاء على مستوى المصدر على هذه المعلومات ، مثل الأمر next ، الذي يحدد نقطة توقف في عنوان السطر التالي ، أمر طباعة يعتمد على الأنواع لعرض المتغيرات بالنوع الصحيح ( char ، int ، عائم ، بدلاً من ثنائي / سداسي عشري!).

    الكلمات الأخيرة

    لقد رأينا العديد من الجوانب الداخلية لمصحح الأخطاء ، لذلك سأقول بضع كلمات من النقاط الأخيرة:

    • تتبع المكدس "غير مفكك" من الإطار الحالي ( $ sp و $ bp / # fp ) لأعلى ، إطار واحد في كل مرة. تم العثور على اسم الدوال والمعلمات والمتغيرات المحلية في معلومات التصحيح.
    • يتم تنفيذ نقاط المراقبة (إن وجدت) بمساعدة المعالج: اكتب في سجلاته العناوين التي يجب يتم رصدها ، وسوف تثير استثناء عند قراءة أو كتابة الذاكرة. إذا لم يكن هذا الدعم متاحًا ، أو إذا طلبت نقاط مراقبة أكثر مما يدعمه المعالج ... فحينئذٍ يعود مصحح الأخطاء إلى نقاط المراقبة "المصنوعة يدويًا": قم بتنفيذ تعليمات التطبيق عن طريق التعليمات ، وتحقق مما إذا كانت العملية الحالية تلمس عنوانًا محدد المراقبة . نعم ، هذا بطيء جدًا!

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

  • نقاط التوقف الشرطية هي نقاط توقف عادية ، فيما عدا أنه داخليًا ، يتحقق مصحح الأخطاء من الشروط قبل إعطاء التحكم للمستخدم. في حالة عدم مطابقة الشرط يستمر التنفيذ بصمت.
  • واللعب بـ gdb gdb ، أو أفضل (طريقة أفضل في الواقع) ، gdb --pid $ (pidof gdb) ، لأن اثنين من مصحح الأخطاء في نفس المحطة جنونية :-). شيء رائع آخر للتعلم هو تصحيح أخطاء النظام:

    qemu-system-i386 -gdb tcp :: 1234 gdb --pid $ (pidof qemu-system-i386) gdb / boot / vmlinuz --exec "مضيف محلي بعيد الهدف: 1234"

    لكنني سأحتفظ بذلك لمقال آخر!

    ٪٪ item_read_more_button ٪٪

    3 تعليقات

    1. It writes an invalid instruction at this location. What ever this instruction, it just has to be invalid.

      On x86 at least, it is a valid instruction. INT3, or CC in hex. There are also the debug registers which implement breakpoints without modifying any code, although it's limited to a maximum of 4 at once.

      Characterising gdb as a "C debugger" is quite appropriate — try to debug the Asm directly with it is an excruciating experience.

    2. It writes an invalid instruction at this location. What ever this instruction, it just has to be invalid.

      RISC-V actually did this in a special instruction called ebreak. It can change the CPU privileged mode into Debug Mode.

    ترك الرد

    من فضلك ادخل تعليقك
    من فضلك ادخل اسمك هنا