Scroll to navigation

ptrace(2) System Calls Manual ptrace(2)

الاسم

ptrace - تتبع العمليات

المكتبة

مكتبة سي المعيارية (libc، -lc)

موجز

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request op, pid_t pid,
            void *addr, void *data);

الوصف

يوفر استدعاء النظام ptrace() وسيلة تُمكّن عملية واحدة (المُتتبِّع "tracer") من مراقبة والتحكم في تنفيذ عملية أخرى (المُتتبَّع "tracee")، وفحص وتغيير ذاكرة وسجلات المُتتبَّع. يُستخدم هذا الاستدعاء بشكل رئيس لترسيخ تنقيح نقاط التوقف وتتبع استدعاءات النظام.

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


ptrace(PTRACE_foo, pid, ...)

حيث pid هو معرف الخيط لخيط لينكس المقابل.

(لاحظ أنه في هذه الصفحة، تعني "العملية متعددة الخيوط" مجموعة خيوط تتكون من خيوط أُنشئت باستخدام علامة CLONE_THREAD في clone(2).)

يمكن لعملية أن تبدأ تتبعاً عبر استدعاء fork(2) وجعل الابن الناتج ينفذ PTRACE_TRACEME، يتبعه (عادةً) execve(2). بدلاً من ذلك، يمكن لعملية أن تبدأ تتبع عملية أخرى باستخدام PTRACE_ATTACH أو PTRACE_SEIZE.

أثناء التتبع، سيتوقف المُتتبَّع في كل مرة تُسلّم فيها إشارة، حتى لو كانت الإشارة مهملة. (الاستثناء هو SIGKILL، التي تملك تأثيرها المعتاد). سيُخطر المُتتبِّع عند استدعائه القادم لـ waitpid(2) (أو أحد استدعاءات نظام "الانتظار" ذات الصلة)؛ سيُعيد ذلك الاستدعاء قيمة status تحتوي على معلومات تشير إلى سبب التوقف في المُتتبَّع. وبينما يكون المُتتبَّع متوقفاً، يمكن للمُتتبِّع استخدام عمليات ptrace متنوعة لفحص وتعديل المُتتبَّع. ثم يجعل المُتتبِّع المُتتبَّعَ يواصل العمل، مع إمكانية تجاهل الإشارة المُسلّمة (أو حتى تسليم إشارة مختلفة بدلاً عنها).

إذا لم يكن خيار PTRACE_O_TRACEEXEC سارياً، فإن جميع استدعاءات execve(2) الناجحة من قِبل العملية المُتتبَّعة ستؤدي إلى إرسال إشارة SIGTRAP إليها، مما يعطي الأب فرصة للسيطرة قبل أن يبدأ البرنامج الجديد في التنفيذ.

عندما ينتهي المُتتبِّع من التتبع، يمكنه جعل المُتتبَّع يواصل التنفيذ في وضع طبيعي وغير مُتتبَّع عبر PTRACE_DETACH.

تحدد قيمة op العملية التي ستُنفذ:

تُشير إلى أن هذه العملية سيتبعها والدها. لا ينبغي لعملية أن تقوم بهذه العملية إلا إذا كان والدها يتوقع تتبعها. (تُهمل pid و addr و data.)
تُستخدم عملية PTRACE_TRACEME من قِبل المُتتبَّع فقط؛ بينما تُستخدم العمليات المتبقية من قِبل المُتتبِّع فقط. في العمليات التالية، يحدد pid معرف الخيط للمُتتبَّع المراد التعامل معه. بالنسبة للعمليات بخلاف PTRACE_ATTACH و PTRACE_SEIZE و PTRACE_INTERRUPT و PTRACE_KILL، يجب أن يكون المُتتبَّع متوقفاً.
تقرأ كلمة عند العنوان addr في ذاكرة المُتتبَّع، وتُعيد الكلمة كنتيجة لاستدعاء ptrace(). لا يملك لينكس مساحات عناوين منفصلة للنص والبيانات، لذا فإن هاتين العمليتين متكافئتان حالياً. (تُهمل data؛ لكن انظر NOTES.)
تقرأ كلمة عند الإزاحة addr في منطقة USER الخاصة بالمُتتبَّع، والتي تحوي السجلات ومعلومات أخرى عن العملية (انظر <sys/user.h>). تُعاد الكلمة كنتيجة لاستدعاء ptrace(). عادةً، يجب أن تكون الإزاحة محاذية للكلمة (word-aligned)، رغم أن هذا قد يختلف حسب البنية. انظر NOTES. (تُهمل data؛ لكن انظر NOTES.)
تنسخ الكلمة data إلى العنوان addr في ذاكرة المُتتبَّع. وكما هو الحال في PTRACE_PEEKTEXT و PTRACE_PEEKDATA، فإن هاتين العمليتين متكافئتان حالياً.
تنسخ الكلمة data إلى الإزاحة addr في منطقة USER الخاصة بالمُتتبَّع. وكما هو الحال في PTRACE_PEEKUSER، يجب أن تكون الإزاحة عادةً محاذية للكلمة. وحفاظاً على سلامة النواة، فإن بعض التعديلات على منطقة USER غير مسموح بها.
تنسخ سجلات المُتتبَّع العامة أو سجلات الفاصلة العائمة، على التوالي، إلى العنوان data في المُتتبِّع. انظر <sys/user.h> للحصول على معلومات حول تنسيق هذه البيانات. (تُهمل addr). لاحظ أن أنظمة SPARC تكون فيها معاني data و addr معكوسة؛ أي تُهمل data وتُنسخ السجلات إلى العنوان addr. لا تتوفر PTRACE_GETREGS و PTRACE_GETFPREGS في جميع البنيات.
تقرأ سجلات المُتتبَّع. يحدد addr، بطريقة تعتمد على البنية، نوع السجلات التي ستُقرأ. تؤدي NT_PRSTATUS (بالقيمة الرقمية 1) عادةً إلى قراءة السجلات العامة. إذا كان المعالج يحتوي، على سبيل المثال، سجلات فاصلة عائمة أو سجلات متجهية، فيمكن جلبها عبر ضبط addr إلى ثابت NT_foo المقابل. يشير data إلى struct iovec، الذي يصف موقع وحجم المخزن المؤقت الوجهة. عند العودة، تُعدل النواة iov.len للإشارة إلى عدد البايتات الفعلي الذي أُعيد.
تُعدل سجلات المُتتبَّع العامة أو سجلات الفاصلة العائمة، على التوالي، من العنوان data في المُتتبِّع. وكما في PTRACE_POKEUSER، قد تُمنع بعض تعديلات السجلات العامة. (تُهمل addr). لاحظ أن أنظمة SPARC تكون فيها معاني data و addr معكوسة؛ أي تُهمل data وتُنسخ السجلات من العنوان addr. لا تتوفر PTRACE_SETREGS و PTRACE_SETFPREGS في جميع البنيات.
تُعدل سجلات المُتتبَّع. معنى addr و data مماثل لـ PTRACE_GETREGSET.
تجلب معلومات حول الإشارة التي تسببت في التوقف. تنسخ بنية siginfo_t (انظر sigaction(2)) من المُتتبَّع إلى العنوان data في المُتتبِّع. (تُهمل addr).
تضبط معلومات الإشارة: تنسخ بنية siginfo_t من العنوان data في المُتتبِّع إلى المُتتبَّع. سيؤثر هذا فقط على الإشارات التي كانت ستُسلّم بشكل طبيعي إلى المُتتبَّع والتقطها المُتتبِّع. قد يصعب تمييز هذه الإشارات الطبيعية عن الإشارات الاصطناعية التي يولدها ptrace() نفسه. (تُهمل addr).
تجلب بنيات siginfo_t دون إزالة الإشارات من الطابور. يشير addr إلى بنية ptrace_peeksiginfo_args التي تحدد الموضع الترتيبي الذي يجب أن يبدأ منه نسخ الإشارات، وعدد الإشارات المراد نسخها. تُنسخ بنيات siginfo_t إلى المخزن المؤقت الذي يشير إليه data. تحتوي القيمة المعادة على عدد الإشارات المنسوخة (يشير الصفر إلى عدم وجود إشارة تقابل الموضع الترتيبي المحدد). داخل بنيات siginfo المعادة، يتضمن الحقل si_code معلومات (__SI_CHLD و __SI_FAULT، إلخ) لا تُكشف عادةً لمساحة المستخدم.


struct ptrace_peeksiginfo_args {

u64 off; /* الموضع الترتيبي في الطابور الذي يبدأ منه
نسخ الإشارات */
u32 flags; /* PTRACE_PEEKSIGINFO_SHARED أو 0 */
s32 nr; /* عدد الإشارات المراد نسخها */ };

حالياً، توجد علامة واحدة فقط، وهي PTRACE_PEEKSIGINFO_SHARED، لتفريغ الإشارات من طابور إشارات العملية بالكامل. إذا لم تُضبط هذه العلامة، تُقرأ الإشارات من طابور الخيط المحدد.
تضع نسخة من قناع الإشارات المحجوبة (انظر sigprocmask(2)) في المخزن المؤقت الذي يشير إليه data، والذي ينبغي أن يكون مؤشراً إلى مخزن من النوع sigset_t. يحتوي المعطى addr على حجم المخزن المؤقت الذي يشير إليه data (أي sizeof(sigset_t)).
تغير قناع الإشارات المحجوبة (انظر sigprocmask(2)) إلى القيمة المحددة في المخزن المؤقت الذي يشير إليه data، والذي ينبغي أن يكون مؤشراً إلى مخزن من النوع sigset_t. يحتوي المعطى addr على حجم المخزن المؤقت الذي يشير إليه data (أي sizeof(sigset_t)).
تضبط خيارات ptrace من data. (تُهمل addr). تُفسر data كقناع بتات للخيارات، والتي تُحدد بالعلامات التالية:
تُرسل إشارة SIGKILL إلى المُتتبَّع إذا خرج المُتتبِّع. هذا الخيار مفيد لمسؤولي سجون ptrace (jailers) الذين يريدون ضمان عدم هروب المُتتبَّعين من سيطرة المُتتبِّع أبداً.
توقف المُتتبَّع عند استدعاء clone(2) القادم وتبدأ آلياً بتتبع العملية المنسوخة حديثاً، والتي ستبدأ بإشارة SIGSTOP، أو PTRACE_EVENT_STOP إذا استُخدم PTRACE_SEIZE. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
يمكن جلب معرف العملية (PID) للعملية الجديدة باستخدام PTRACE_GETEVENTMSG.
قد لا يمسك هذا الخيار استدعاءات clone(2) في جميع الحالات. إذا استدعى المُتتبَّع clone(2) مع علامة CLONE_VFORK، ستُسلّم PTRACE_EVENT_VFORK بدلاً من ذلك إذا كان PTRACE_O_TRACEVFORK مضبوطاً؛ وإلا إذا استدعى المُتتبَّع clone(2) مع ضبط إشارة الخروج إلى SIGCHLD، ستُسلّم PTRACE_EVENT_FORK إذا كان PTRACE_O_TRACEFORK مضبوطاً.
توقف المُتتبَّع عند استدعاء execve(2) القادم. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
إذا لم يكن الخيط المنفِّذ هو قائد مجموعة الخيوط، يُعاد تعيين معرف الخيط إلى معرف قائد المجموعة قبل هذا التوقف. منذ لينكس 3.0، يمكن جلب معرف الخيط السابق باستخدام PTRACE_GETEVENTMSG.
توقف المُتتبَّع عند الخروج. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))
يمكن جلب حالة خروج المُتتبَّع باستخدام PTRACE_GETEVENTMSG.
يتوقف المُتتبَّع مبكراً أثناء خروج العملية، عندما تكون السجلات لا تزال متاحة، مما يسمح للمُتتبِّع برؤية مكان وقوع الخروج، بينما يتم إشعار الخروج الطبيعي بعد انتهاء خروج العملية. ورغم توفر السياق، لا يمكن للمُتتبِّع منع وقوع الخروج عند هذه النقطة.
توقف المُتتبَّع عند استدعاء fork(2) القادم وتبدأ آلياً بتتبع العملية المتفرعة حديثاً، والتي ستبدأ بإشارة SIGSTOP، أو PTRACE_EVENT_STOP إذا استُخدم PTRACE_SEIZE. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8))
يمكن جلب معرف العملية (PID) للعملية الجديدة باستخدام PTRACE_GETEVENTMSG.
عند تسليم فخاخ استدعاء النظام، تضبط البت 7 في رقم الإشارة (أي تسلّم SIGTRAP|0x80). هذا يسهل على المُتتبِّع تمييز الفخاخ الطبيعية عن تلك الناتجة عن استدعاء نظام.
توقف المُتتبَّع عند استدعاء vfork(2) القادم وتبدأ آلياً بتتبع العملية المتفرعة (vforked) حديثاً، والتي ستبدأ بإشارة SIGSTOP، أو PTRACE_EVENT_STOP إذا استُخدم PTRACE_SEIZE. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8))
يمكن جلب معرف العملية (PID) للعملية الجديدة باستخدام PTRACE_GETEVENTMSG.
توقف المُتتبَّع عند اكتمال استدعاء vfork(2) القادم. سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8))
يمكن (منذ لينكس 2.6.18) جلب معرف العملية (PID) للعملية الجديدة باستخدام PTRACE_GETEVENTMSG.
توقف المُتتبَّع عند انطلاق قاعدة SECCOMP_RET_TRACE الخاصة بـ seccomp(2). سيُعيد استدعاء waitpid(2) من قِبل المُتتبِّع قيمة status بحيث

status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))
بينما يطلق هذا توقف PTRACE_EVENT، إلا أنه مشابه لتوقف syscall-enter-stop. للتفاصيل، انظر الملاحظة حول PTRACE_EVENT_SECCOMP أدناه. يمكن جلب بيانات رسالة حدث seccomp (من جزء SECCOMP_RET_DATA لقاعدة مرشح seccomp) باستخدام PTRACE_GETEVENTMSG.
تُعلّق حماية seccomp الخاصة بالمُتتبَّع. ينطبق هذا بغض النظر عن الوضع، ويمكن استخدامه عندما لا يكون المُتتبَّع قد ثبّت مرشحات seccomp بعد. أي أن حالة الاستخدام الصالحة هي تعليق حماية seccomp للمُتتبَّع قبل تثبيتها من قِبله، ثم السماح له بتثبيتها، ثم مسح هذه العلامة عندما يجب استئناف عمل المرشحات. يتطلب ضبط هذا الخيار أن يمتلك المُتتبِّع صلاحية CAP_SYS_ADMIN، وألا يكون لديه أي حماية seccomp مثبتة، وألا يكون خيار PTRACE_O_SUSPEND_SECCOMP مضبوطاً على نفسه.
تجلب رسالة (كـ unsigned long) حول حدث ptrace الذي وقع للتو، وتضعها في العنوان data في المُتتبِّع. بالنسبة لـ PTRACE_EVENT_EXIT، تكون هذه حالة خروج المُتتبَّع. ولـ PTRACE_EVENT_FORK و PTRACE_EVENT_VFORK و PTRACE_EVENT_VFORK_DONE و PTRACE_EVENT_CLONE، تكون هذه معرف العملية (PID) للعملية الجديدة. ولـ PTRACE_EVENT_SECCOMP، تكون هذه قيمة SECCOMP_RET_DATA لمرشح seccomp(2) المرتبطة بالقاعدة التي انطلقت. (تُهمل addr).
تُعيد تشغيل عملية المُتتبَّع المتوقفة. إذا كانت data غير صفرية، تُفسر على أنها رقم الإشارة المراد تسليمها للمُتتبَّع؛ وإلا فلا تُسلّم أي إشارة. وبذلك، يمكن للمُتتبِّع، على سبيل المثال، التحكم في تسليم الإشارة المرسلة إلى المُتتبَّع من عدمه. (تُهمل addr).
تُعيد تشغيل المُتتبَّع المتوقف كما في PTRACE_CONT، ولكنها تُرتّب لتوقفه عند الدخول القادم إلى استدعاء نظام أو الخروج منه، أو بعد تنفيذ تعليمة واحدة، على التوالي. (سيتوقف المُتتبَّع أيضًا، كالمعتاد، عند استلام إشارة). من منظور المُتتبِّع، سيبدو أن المُتتبَّع قد توقف بسبب استلام SIGTRAP. لذا، بالنسبة لـ PTRACE_SYSCALL مثلاً، تكمن الفكرة في فحص معطيات استدعاء النظام عند التوقف الأول، ثم القيام بـ PTRACE_SYSCALL آخر وفحص القيمة المعادة من استدعاء النظام عند التوقف الثاني. يُعامل المعطى data كما في PTRACE_CONT. (تُهمل addr).
عندما يكون في syscall-enter-stop، غيّر رقم استدعاء النظام الذي يوشك أن يُنفذ إلى الرقم المحدد في وسيط data. يُتجاهل الوسيط addr. هذه العملية مدعومة حاليًا فقط على arm (و arm64، وإن كان ذلك للتوافق مع الإصدارات السابقة فقط)، ولكن معظم المعماريات الأخرى لديها وسائل أخرى لتحقيق ذلك (عادةً عن طريق تغيير السجل الذي مررت فيه شيفرة نطاق المستخدم رقم استدعاء النظام).
بالنسبة لـ PTRACE_SYSEMU، استمر وتوقف عند الدخول إلى استدعاء النظام التالي، والذي لن يُنفذ. انظر التوثيق الخاص بـ syscall-stops أدناه. بالنسبة لـ PTRACE_SYSEMU_SINGLESTEP، افعل الشيء نفسه ولكن نفذ أيضًا خطوة واحدة إذا لم يكن استدعاء نظام. يُستخدم هذا الاستدعاء من قبل برامج مثل User Mode Linux التي تريد محاكاة جميع استدعاءات نظام المتتبَّع. يُعامل الوسيط data كما هو الحال في PTRACE_CONT. يُتجاهل الوسيط addr. هذه العمليات مدعومة حاليًا فقط على x86.
أعد تشغيل المتتبَّع المتوقف، ولكن امنعه من التنفيذ. الحالة الناتجة للمتتبَّع مشابهة لعملية أُوقفت بواسطة SIGSTOP (أو أي إشارة إيقاف أخرى). انظر القسم الفرعي "group-stop" للحصول على معلومات إضافية. يعمل PTRACE_LISTEN فقط على المتتبَّعين المرفقين بواسطة PTRACE_SEIZE.
أرسل SIGKILL إلى المتتبَّع لإنهاء عمله. (يُتجاهل addr و data.)
هذه العملية مهجورة؛ لا تستخدمها! بدلاً من ذلك، أرسل SIGKILL مباشرة باستخدام kill(2) أو tgkill(2). تكمن المشكلة في PTRACE_KILL في أنها تتطلب أن يكون المتتبَّع في حالة signal-delivery-stop، وإلا فقد لا تعمل (أي قد تكتمل بنجاح ولكنها لن تقتل المتتبَّع). على النقيض من ذلك، فإن إرسال SIGKILL مباشرة ليس له مثل هذا القيد.
أوقف المتتبَّع. إذا كان المتتبَّع يعمل أو ينام في مساحة النواة وكان PTRACE_SYSCALL ساري المفعول، يُقاطع استدعاء النظام ويُبلغ عن syscall-exit-stop. (يُعاد تشغيل استدعاء النظام المقاطع عند إعادة تشغيل المتتبَّع). إذا كان المتتبَّع قد توقف بالفعل بسبب إشارة وأُرسل إليه PTRACE_LISTEN، يتوقف المتتبَّع مع PTRACE_EVENT_STOP ويعيد WSTOPSIG(status) إشارة التوقف. إذا وُلد أي توقف ptrace-stop آخر في نفس الوقت (على سبيل المثال، إذا أُرسلت إشارة إلى المتتبَّع)، يحدث توقف ptrace-stop هذا. إذا لم ينطبق أي مما سبق (على سبيل المثال، إذا كان المتتبَّع يعمل في مساحة المستخدم)، فإنه يتوقف مع PTRACE_EVENT_STOP مع كون WSTOPSIG(status) مساوياً لـ SIGTRAP. يعمل PTRACE_INTERRUPT فقط على المتتبَّعين المرفقين بواسطة PTRACE_SEIZE.
أرفق العملية المحددة في pid، مما يجعلها متتبَّعاً للعملية المستدعِية. تُرسل إشارة SIGSTOP إلى المتتبَّع، ولكن ليس بالضرورة أن يكون قد توقف بانتهاء هذا الاستدعاء؛ استخدم waitpid(2) لانتظار توقف المتتبَّع. انظر القسم الفرعي "Attaching and detaching" لمزيد من المعلومات. (يُتجاهل addr و data.)
تُحكم صلاحية تنفيذ PTRACE_ATTACH بواسطة فحص وضع وصول ptrace PTRACE_MODE_ATTACH_REALCREDS؛ انظر أدناه.
أرفق العملية المحددة في pid، مما يجعلها متتبَّعاً للعملية المستدعِية. على عكس PTRACE_ATTACH، لا يوقف PTRACE_SEIZE العملية. يُبلغ عن توقفات المجموعة (Group-stops) كـ PTRACE_EVENT_STOP ويعيد WSTOPSIG(status) إشارة التوقف. الأبناء المرفقون آليًا يتوقفون مع PTRACE_EVENT_STOP ويعيد WSTOPSIG(status) القيمة SIGTRAP بدلاً من إرسال إشارة SIGSTOP إليهم. لا يرسل execve(2) إشارة SIGTRAP إضافية. فقط العمليات المرفقة عبر PTRACE_SEIZE يمكنها قبول أوامر PTRACE_INTERRUPT و PTRACE_LISTEN. السلوك "المستولى عليه" الموصوف للتو يورث للأبناء الذين أُرفقوا آليًا باستخدام PTRACE_O_TRACEFORK و PTRACE_O_TRACEVFORK و PTRACE_O_TRACECLONE. يجب أن يكون addr صفرًا. يحتوي data على قناع بت لخيارات ptrace لتنشيطها فورًا.
تُحكم صلاحية تنفيذ PTRACE_SEIZE بواسطة فحص وضع وصول ptrace PTRACE_MODE_ATTACH_REALCREDS؛ انظر أدناه.
تسمح هذه العملية للمتتبِّع بتفريغ مرشحات BPF الكلاسيكية الخاصة بالمتتبَّع.
addr هو عدد صحيح يحدد فهرس المرشح المراد تفريغه. أحدث مرشح ثُبت له الفهرس 0. إذا كان addr أكبر من عدد المرشحات المثبتة، تفشل العملية بالخطأ ENOENT.
data هو إما مؤشر إلى مصفوفة struct sock_filter كبيرة بما يكفي لتخزين برنامج BPF، أو NULL إذا كان البرنامج لن يُخزن.
عند النجاح، تكون القيمة المعادة هي عدد التعليمات في برنامج BPF. إذا كان data هو NULL، فيمكن استخدام هذه القيمة المعادة لتحديد حجم مصفوفة struct sock_filter الممرة في استدعاء لاحق بشكل صحيح.
تفشل هذه العملية بالخطأ EACCES إذا لم يكن لدى المستدعِي قدرة CAP_SYS_ADMIN أو إذا كان المستدعِي في وضع seccomp الصارم أو المرشح. إذا لم يكن المرشح المشار إليه بواسطة addr مرشح BPF كلاسيكيًا، تفشل العملية بالخطأ EMEDIUMTYPE.
هذه العملية متاحة إذا ضُبطت النواة بكل من خياري CONFIG_SECCOMP_FILTER و CONFIG_CHECKPOINT_RESTORE.
أعد تشغيل المتتبَّع المتوقف كما في PTRACE_CONT، ولكن افصل عنه أولاً. تحت نظام لينكس، يمكن فصل المتتبَّع بهذه الطريقة بغض النظر عن الطريقة التي استُخدمت لبدء التتبع. (يُتجاهل addr.)
تؤدي هذه العملية مهمة مماثلة لـ get_thread_area(2). حيث تقرأ مدخل TLS في GDT الذي أُعطي فهرسه في addr، وتضع نسخة من المدخل في struct user_desc الذي يشير إليه data. (على النقيض من get_thread_area(2)، يُتجاهل entry_number الخاص بـ struct user_desc.)
تؤدي هذه العملية مهمة مماثلة لـ set_thread_area(2). حيث تضبط مدخل TLS في GDT الذي أُعطي فهرسه في addr، وتعين له البيانات المتوفرة في struct user_desc الذي يشير إليه data. (على النقيض من set_thread_area(2)، يُتجاهل entry_number الخاص بـ struct user_desc؛ وبعبارة أخرى، لا يمكن استخدام عملية ptrace هذه لتخصيص مدخل TLS حر.)
استرجع معلومات حول استدعاء النظام الذي تسبب في التوقف. توضع المعلومات في الذاكرة الوسيطة التي يشير إليها الوسيط data، والذي يجب أن يكون مؤشرًا إلى ذاكرة وسيطة من النوع struct ptrace_syscall_info. يحتوي الوسيط addr على حجم الذاكرة الوسيطة التي يشير إليها الوسيط data (أي sizeof(struct ptrace_syscall_info)). تحتوي القيمة المعادة على عدد البايتات المتاحة لتكتبها النواة. إذا تجاوز حجم البيانات التي ستكتبها النواة الحجم المحدد بواسطة الوسيط addr، تُقتطع بيانات المخرجات.
تحتوي البنية ptrace_syscall_info على الحقول التالية:

struct ptrace_syscall_info {

__u8 op; /* نوع توقف استدعاء النظام */
__u8 reserved; /* محجوز للاستخدام المستقبلي، يجب أن يكون صفراً */
__u16 flags; /* محجوز للاستخدام المستقبلي، يجب أن يكون صفراً */
__u32 arch; /* قيمة AUDIT_ARCH_*؛ انظر seccomp(2) */
__u64 instruction_pointer; /* مؤشر تعليمات المعالج */
__u64 stack_pointer; /* مؤشر مكدس المعالج */
union {
struct { /* op == PTRACE_SYSCALL_INFO_ENTRY */
__u64 nr; /* رقم استدعاء النظام */
__u64 args[6]; /* وسائط استدعاء النظام */
} entry;
struct { /* op == PTRACE_SYSCALL_INFO_EXIT */
__s64 rval; /* قيمة إرجاع استدعاء النظام */
__u8 is_error; /* علامة خطأ استدعاء النظام؛
قيمة منطقية: هل يحتوي rval على
قيمة خطأ (-ERRCODE) أم
قيمة إرجاع غير خاطئة؟ */
} exit;
struct { /* op == PTRACE_SYSCALL_INFO_SECCOMP */
__u64 nr; /* رقم استدعاء النظام */
__u64 args[6]; /* وسائط استدعاء النظام */
__u32 ret_data; /* جزء SECCOMP_RET_DATA من
قيمة إرجاع SECCOMP_RET_TRACE */
} seccomp;
}; };

تُعرف حقول op و arch و instruction_pointer و stack_pointer لجميع أنواع توقفات استدعاء نظام ptrace. بقية البنية هي اتحاد (union)؛ يجب قراءة الحقول ذات المغزى فقط لنوع توقف استدعاء النظام المحدد بواسطة حقل op.
يحتوي حقل op على إحدى القيم التالية (المعرفة في <linux/ptrace.h>) التي تشير إلى نوع التوقف الذي حدث وأي جزء من الاتحاد (union) قد مُلئ:
يحتوي مكون entry في الاتحاد على معلومات تتعلق بتوقف الدخول في استدعاء نظام.
يحتوي مكون exit في الاتحاد على معلومات تتعلق بتوقف الخروج من استدعاء نظام.
يحتوي مكون seccomp في الاتحاد على معلومات تتعلق بتوقف PTRACE_EVENT_SECCOMP.
لا يوجد أي مكون من الاتحاد يحتوي على معلومات ذات صلة.
في حالة توقفات الدخول أو الخروج من استدعاء النظام، تقتصر البيانات المعادة بواسطة PTRACE_GET_SYSCALL_INFO على النوع PTRACE_SYSCALL_INFO_NONE ما لم يُضبط خيار PTRACE_O_TRACESYSGOOD قبل حدوث توقف استدعاء النظام المقابل.
عدل المعلومات المتعلقة باستدعاء النظام الذي تسبب في التوقف. الوسيط data هو مؤشر إلى struct ptrace_syscall_info يحدد معلومات استدعاء النظام المراد ضبطها. يجب ضبط الوسيط addr على sizeof(struct ptrace_syscall_info). يمكن تعديل حقول nr و args و rval فقط.

الموت تحت ptrace

عندما تتلقى عملية (ربما متعددة الخيوط) إشارة قتل (تلك التي ضُبط تصرفها على SIG_DFL وإجراءها المبدئي هو قتل العملية)، تخرج جميع الخيوط. يبلغ المتتبَّعون عن موتهم إلى متتبِّعيهم. يُسلم إشعار بهذا الحدث عبر waitpid(2).

لاحظ أن إشارة القتل ستؤدي أولاً إلى signal-delivery-stop (على متتبَّع واحد فقط)، وفقط بعد حقنها من قبل المتتبِّع (أو بعد إرسالها إلى خيط غير متتبَّع)، سيحدث الموت بسبب الإشارة على جميع المتتبَّعين داخل عملية متعددة الخيوط. (يُشرح مصطلح "signal-delivery-stop" أدناه).

لا يولد SIGKILL حالة signal-delivery-stop، وبالتالي لا يمكن للمتتبِّع قمعها. يقتل SIGKILL حتى داخل استدعاءات النظام (لا يولد syscall-exit-stop قبل الموت بـ SIGKILL). الأثر الصافي هو أن SIGKILL يقتل العملية دائمًا (جميع خيوطها)، حتى لو كانت بعض خيوط العملية خاضعة للتتبع بـ ptrace.

عندما يستدعي المتتبَّع _exit(2)، يبلغ عن موته لمتتبِّعه. لا تتأثر الخيوط الأخرى.

عندما ينفذ أي خيط exit_group(2)، يبلغ كل متتبَّع في مجموعة خيوطه عن موته لمتتبِّعه.

إذا كان خيار PTRACE_O_TRACEEXIT مفعلًا، سيحدث PTRACE_EVENT_EXIT قبل الموت الفعلي. ينطبق هذا على حالات الخروج عبر exit(2)، و exit_group(2)، والوفيات الناجمة عن الإشارات (باستثناء SIGKILL، اعتمادًا على إصدار النواة؛ انظر BUGS أدناه)، وعندما تُهدم الخيوط عند execve(2) في عملية متعددة الخيوط.

لا يمكن للمتتبِّع أن يفترض وجود المتتبَّع المتوقف بـ ptrace. هناك سيناريوهات عديدة قد يموت فيها المتتبَّع أثناء توقفه (مثل SIGKILL). لذلك، يجب أن يكون المتتبِّع مستعدًا للتعامل مع خطأ ESRCH في أي عملية ptrace. لسوء الحظ، يُعاد نفس الخطأ إذا كان المتتبَّع موجودًا ولكنه ليس متوقفًا بـ ptrace (بالنسبة للأوامر التي تتطلب متتبَّعًا متوقفًا)، أو إذا لم يكن متتبعًا من قبل العملية التي أصدرت استدعاء ptrace. يحتاج المتتبِّع إلى تتبع حالة التوقف/التشغيل للمتتبَّع، وتفسير ESRCH على أنه "مات المتتبَّع بشكل غير متوقع" فقط إذا كان يعلم أن المتتبَّع قد لوحظ وهو يدخل في حالة ptrace-stop. لاحظ أنه لا يوجد ضمان بأن waitpid(WNOHANG) سيبلغ بشكل موثوق عن حالة موت المتتبَّع إذا أعادت عملية ptrace الخطأ ESRCH. قد يعيد waitpid(WNOHANG) القيمة 0 بدلاً من ذلك. بعبارة أخرى، قد يكون المتتبَّع "لم يمت تمامًا بعد"، ولكنه يرفض بالفعل عمليات ptrace.

لا يمكن للمتتبِّع أن يفترض أن المتتبَّع ينهي حياته دائمًا بالإبلاغ عن WIFEXITED(status) أو WIFSIGNALED(status)؛ فهناك حالات لا يحدث فيها ذلك. على سبيل المثال، إذا قام خيط آخر غير قائد مجموعة الخيوط بتنفيذ execve(2)، فإنه يختفي؛ ولن يُرى معرف العملية (PID) الخاص به مرة أخرى، وسيُبلغ عن أي توقفات ptrace لاحقة تحت معرف العملية الخاص بقائد مجموعة الخيوط.

حالات التوقف

يمكن أن يكون المتتبَّع في حالتين: يعمل أو متوقف. لأغراض ptrace، فإن المتتبَّع المحجوز في استدعاء نظام (مثل read(2) أو pause(2)، إلخ) يُعتبر مع ذلك يعمل، حتى لو كان المتتبَّع محجوزًا لفترة طويلة. حالة المتتبَّع بعد PTRACE_LISTEN هي منطقة رمادية إلى حد ما: فهو ليس في أي ptrace-stop (أوامر ptrace لن تعمل عليه، وسيقوم بتسليم إشعارات waitpid(2))، ولكنه قد يُعتبر أيضًا "متوقفًا" لأنه لا ينفذ التعليمات (لم يُجدول)، وإذا كان في حالة group-stop قبل PTRACE_LISTEN، فلن يستجيب للإشارات حتى تُتلقى إشارة SIGCONT.

هناك أنواع عديدة من الحالات عندما يكون المتتبَّع متوقفًا، وغالبًا ما يُخلط بينها في مناقشات ptrace. لذلك، من المهم استخدام مصطلحات دقيقة.

في صفحة الدليل هذه، تُسمى أي حالة توقف يكون فيها المتتبَّع جاهزًا لقبول أوامر ptrace من المتتبِّع بـ ptrace-stop. يمكن تقسيم توقفات ptrace أكثر إلى signal-delivery-stop و group-stop و syscall-stop و PTRACE_EVENT stops، وما إلى ذلك. توصف حالات التوقف هذه بالتفصيل أدناه.

عندما يدخل المتتبَّع العامل في حالة ptrace-stop، فإنه يخطر متتبِّعه باستخدام waitpid(2) (أو أحد استدعاءات نظام "الانتظار" الأخرى). تفترض معظم صفحة الدليل هذه أن المتتبِّع ينتظر بـ:


pid = waitpid(pid_or_minus_1, &status, __WALL);

يُبلغ عن المتتبَّعين المتوقفين بـ ptrace كقيم معادة مع pid أكبر من 0 وكون WIFSTOPPED(status) صحيحًا.

لا يتضمن علم __WALL علمي WSTOPPED و WEXITED، لكنه يتضمن وظائفهما ضمنيًا.

لا يوصى بضبط علم WCONTINUED عند استدعاء waitpid(2): حالة "الاستمرار" تكون لكل عملية واستهلاكها يمكن أن يربك الأب الحقيقي للمتتبَّع.

قد يؤدي استخدام علم WNOHANG إلى إعادة waitpid(2) للقيمة 0 ("لا توجد نتائج انتظار متاحة بعد") حتى لو كان المتتبِّع يعلم أنه يجب أن يكون هناك إشعار. مثال:


errno = 0;
ptrace(PTRACE_CONT, pid, 0L, 0L);
if (errno == ESRCH) {

/* المتتبَّع ميت */
r = waitpid(tracee, &status, __WALL | WNOHANG);
/* يمكن أن يظل r صفراً هنا! */ }

توجد الأنواع التالية من توقفات ptrace-stops: signal-delivery-stops و group-stops و PTRACE_EVENT stops و syscall-stops. يُبلغ عنها جميعًا بواسطة waitpid(2) مع كون WIFSTOPPED(status) صحيحًا. يمكن التمييز بينها بفحص القيمة status>>8، وإذا كان هناك غموض في تلك القيمة، فعن طريق الاستعلام عن PTRACE_GETSIGINFO. (ملاحظة: لا يمكن استخدام ماكرو WSTOPSIG(status) لإجراء هذا الفحص، لأنه يعيد القيمة (status>>8) & 0xff.)

توقف تسليم الإشارة (Signal-delivery-stop)

عندما تتلقى عملية (ربما متعددة الخيوط) أي إشارة باستثناء SIGKILL، تختار النواة خيطًا عشوائيًا يتعامل مع الإشارة. (إذا وُلدت الإشارة باستخدام tgkill(2)، فيمكن للمستدعِي تحديد الخيط المستهدف صراحةً). إذا كان الخيط المختار متتبَّعًا، فإنه يدخل في حالة signal-delivery-stop. في هذه المرحلة، لم تُسلم الإشارة بعد إلى العملية، ويمكن للمتتبِّع قمعها. إذا لم يقمع المتتبِّع الإشارة، فإنه يمرر الإشارة إلى المتتبَّع في عملية إعادة تشغيل ptrace التالية. تُسمى هذه الخطوة الثانية من تسليم الإشارة بـ حقن الإشارة (signal injection) في صفحة الدليل هذه. لاحظ أنه إذا كانت الإشارة محجوبة، فلن يحدث signal-delivery-stop حتى يُرفع الحجب عن الإشارة، مع الاستثناء المعتاد وهو أن SIGSTOP لا يمكن حجبه.

يلاحظ المتتبِّع حالة Signal-delivery-stop عن طريق إعادة waitpid(2) لقيمة مع كون WIFSTOPPED(status) صحيحًا، مع إعادة الإشارة بواسطة WSTOPSIG(status). إذا كانت الإشارة هي SIGTRAP، فقد يكون هذا نوعًا مختلفًا من ptrace-stop؛ انظر قسمي "Syscall-stops" و "execve" أدناه للتفاصيل. إذا أعاد WSTOPSIG(status) إشارة توقف، فقد يكون هذا group-stop؛ انظر أدناه.

حقن الإشارة وقمعها

بعد ملاحظة signal-delivery-stop من قبل المتتبِّع، يجب على المتتبِّع إعادة تشغيل المتتبَّع بالاستدعاء


ptrace(PTRACE_restart, pid, 0, sig)

حيث PTRACE_restart هي إحدى عمليات إعادة تشغيل ptrace. إذا كان sig هو 0، فلن تُسلم الإشارة. بخلاف ذلك، تُسلم الإشارة sig. تُسمى هذه العملية حقن الإشارة (signal injection) في صفحة الدليل هذه، لتمييزها عن signal-delivery-stop.

قد تكون قيمة sig مختلفة عن قيمة WSTOPSIG(status): حيث يمكن للمتتبِّع أن يتسبب في حقن إشارة مختلفة.

لاحظ أن الإشارة المقْموعة تظل تتسبب في عودة استدعاءات النظام قبل الأوان. في هذه الحالة، سيُعاد تشغيل استدعاءات النظام: سيلاحظ المتتبِّع المتتبَّع وهو يعيد تنفيذ استدعاء النظام المقاطع (أو استدعاء النظام restart_syscall(2) لعدد قليل من استدعاءات النظام التي تستخدم آلية مختلفة لإعادة التشغيل) إذا استخدم المتتبِّع PTRACE_SYSCALL. حتى استدعاءات النظام (مثل poll(2)) التي لا يمكن إعادة تشغيلها بعد الإشارة يُعاد تشغيلها بعد قمع الإشارة؛ ومع ذلك، توجد أخطاء برمجية في النواة تتسبب في فشل بعض استدعاءات النظام بـ EINTR على الرغم من عدم حقن إشارة ملحوظة في المتتبَّع.

أوامر إعادة تشغيل ptrace الصادرة في حالات ptrace-stops بخلاف signal-delivery-stop غير مضمونة لحقن إشارة، حتى لو كان sig غير صفري. لا يُبلغ عن أي خطأ؛ وقد يُتجاهل sig غير الصفري ببساطة. يجب ألا يحاول مستخدمو Ptrace "إنشاء إشارة جديدة" بهذه الطريقة: استخدم tgkill(2) بدلاً من ذلك.

إن حقيقة أن عمليات حقن الإشارة قد تُتجاهل عند إعادة تشغيل المتتبَّع بعد حالات توقف ptrace التي ليست signal-delivery-stops هي سبب للارتباك بين مستخدمي ptrace. أحد السيناريوهات النمطية هو أن يلاحظ المتتبِّع حالة group-stop، ويخطئ في اعتبارها signal-delivery-stop، فيعيد تشغيل المتتبَّع بـ


ptrace(PTRACE_restart, pid, 0, stopsig)

بنية حقن stopsig، ولكن يُتجاهل stopsig ويستمر المتتبَّع في العمل.

لإشارة SIGCONT أثر جانبي يتمثل في إيقاظ (جميع خيوط) عملية متوقفة في مجموعة (group-stopped). يحدث هذا الأثر الجانبي قبل signal-delivery-stop. لا يمكن للمتتبِّع قمع هذا الأثر الجانبي (يمكنه فقط قمع حقن الإشارة، مما يتسبب فقط في عدم تنفيذ معالج SIGCONT في المتتبَّع، إذا كان مثل هذا المعالج مثبتًا). في الواقع، الاستيقاظ من group-stop قد يتبعه signal-delivery-stop لإشارة (أو إشارات) بخلاف SIGCONT، إذا كانت معلقة عند تسليم SIGCONT. بعبارة أخرى، قد لا تكون SIGCONT هي أول إشارة يلاحظها المتتبَّع بعد إرسالها.

تتسبب إشارات الإيقاف في دخول (جميع خيوط) العملية في حالة group-stop. يحدث هذا الأثر الجانبي بعد حقن الإشارة، وبالتالي يمكن للمتتبِّع قمعه.

في لينكس 2.4 وما قبله، لا يمكن حقن إشارة SIGSTOP.

يمكن استخدام PTRACE_GETSIGINFO لاستعادة بنية siginfo_t التي تقابل الإشارة المسلمة. يمكن استخدام PTRACE_SETSIGINFO لتعديلها. إذا استُخدم PTRACE_SETSIGINFO لتغيير siginfo_t، فيجب أن يتطابق حقل si_signo والمعامل sig في أمر إعادة التشغيل، وإلا ستكون النتيجة غير محددة.

توقف المجموعة (Group-stop)

عندما تتلقى عملية (قد تكون متعددة الخيوط) إشارة إيقاف، تتوقف جميع الخيوط. وإذا كان بعض هذه الخيوط خاضعًا للتتبع، فإنها تدخل في حالة "توقف المجموعة" (group-stop). لاحظ أن إشارة الإيقاف ستؤدي أولاً إلى "توقف تسليم الإشارة" (signal-delivery-stop) (على متتبَّع واحد فقط)، وفقط بعد أن يتم حقنها بواسطة المتتبِّع (أو بعد إرسالها إلى خيط غير خاضع للتتبع)، سيبدأ "توقف المجموعة" في كافة المتتبَّعين داخل العملية متعددة الخيوط. وكما هو معتاد، يبلغ كل متتبَّع عن "توقف المجموعة" الخاص به بشكل منفصل إلى المتتبِّع المقابل له.

يلاحظ المتتبِّع حالة Group-stop عن طريق إعادة waitpid(2) لقيمة مع كون WIFSTOPPED(status) صحيحًا، مع توفر إشارة التوقف عبر WSTOPSIG(status). تُعاد نفس النتيجة من قبل بعض الفئات الأخرى من ptrace-stops، لذلك فإن الممارسة الموصى بها هي إجراء الاستدعاء


ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo)

يمكن تجنب الاستدعاء إذا لم تكن الإشارة SIGSTOP أو SIGTSTP أو SIGTTIN أو SIGTTOU؛ فهذه الإشارات الأربع فقط هي إشارات توقف. إذا رأى المتتبِّع شيئًا آخر، فلا يمكن أن يكون group-stop. بخلاف ذلك، يحتاج المتتبِّع لاستدعاء PTRACE_GETSIGINFO. إذا فشل PTRACE_GETSIGINFO بـ EINVAL، فهو بالتأكيد group-stop. (رموز فشل أخرى ممكنة، مثل ESRCH ("لا توجد عملية كهذه") إذا قتل SIGKILL المتتبَّع).

إذا أُرفق المتتبَّع باستخدام PTRACE_SEIZE، يُشار إلى group-stop بواسطة PTRACE_EVENT_STOP: القيمة status>>16 == PTRACE_EVENT_STOP. يسمح هذا باكتشاف group-stops دون الحاجة إلى استدعاء PTRACE_GETSIGINFO إضافي.

اعتبارًا من لينكس 2.6.38، بعد أن يرى المتتبِّع حالة ptrace-stop للمتتبَّع وحتى يعيد تشغيله أو يقتله، لن يعمل المتتبَّع، ولن يرسل إشعارات (باستثناء الموت بـ SIGKILL) للمتتبِّع، حتى لو دخل المتتبِّع في استدعاء waitpid(2) آخر.

يتسبب سلوك النواة الموصوف في الفقرة السابقة في حدوث مشكلة في التعامل الشفاف مع إشارات الإيقاف. إذا أعاد المتتبِّع تشغيل المتتبَّع بعد group-stop، فستُتجاهل إشارة الإيقاف فعليًا—إذ لا يظل المتتبَّع متوقفًا بل يعمل. إذا لم يعد المتتبِّع تشغيل المتتبَّع قبل الدخول في استدعاء waitpid(2) التالي، فلن يُبلغ عن إشارات SIGCONT المستقبلية للمتتبِّع؛ وهذا سيجعل إشارات SIGCONT ليس لها أي تأثير على المتتبَّع.

منذ لينكس 3.4، توجد طريقة للتغلب على هذه المشكلة: بدلاً من PTRACE_CONT، يمكن استخدام أمر PTRACE_LISTEN لإعادة تشغيل المتتبَّع بطريقة لا ينفذ فيها تعليمات، بل ينتظر حدثًا جديدًا يمكنه الإبلاغ عنه عبر waitpid(2) (مثل عندما يُعاد تشغيله بواسطة SIGCONT).

توقفات PTRACE_EVENT

إذا ضبط المتتبِّع خيارات PTRACE_O_TRACE_*، سيدخل المتتبَّع في حالات ptrace-stops تُسمى توقفات PTRACE_EVENT.

يلاحظ المتتبِّع توقفات PTRACE_EVENT عن طريق إعادة waitpid(2) لقيمة مع WIFSTOPPED(status)، ويعيد WSTOPSIG(status) القيمة SIGTRAP (أو بالنسبة لـ PTRACE_EVENT_STOP، يعيد إشارة التوقف إذا كان المتتبَّع في حالة group-stop). يُضبط بت إضافي في البايت الأعلى من كلمة الحالة: ستكون القيمة status>>8 هي


((PTRACE_EVENT_foo<<8) | SIGTRAP).

توجد الأحداث التالية:

توقف قبل العودة من vfork(2) أو clone(2) مع وسم CLONE_VFORK. عندما يستأنف المُتتبَّع بعد هذا التوقف، فإنه سينتظر خروج الابن أو تنفيذه لأمر exec قبل متابعة تنفيذه (بمعنى آخر، السلوك المعتاد في vfork(2)).
توقف قبل العودة من fork(2) أو clone(2) مع ضبط إشارة الخروج لتكون SIGCHLD.
توقف قبل العودة من clone(2).
توقف قبل العودة من vfork(2) أو clone(2) مع وسم CLONE_VFORK، ولكن بعد أن يرفع الابن الحظر عن هذا المُتتبَّع بالخروج أو تنفيذ exec.

في جميع حالات التوقف الأربعة الموصوفة أعلاه، يحدث التوقف في الأب (أي المُتتبَّع)، وليس في الخيط المنشأ حديثًا. يمكن استخدام PTRACE_GETEVENTMSG لاسترداد معرف الخيط الجديد.

توقف قبل العودة من execve(2). منذ الإصدار 3.0 من لينكس، يعيد PTRACE_GETEVENTMSG معرف الخيط السابق.
توقف قبل الخروج (بما في ذلك الموت الناتج عن exit_group(2))، أو الموت بسبب إشارة، أو الخروج الناتج عن execve(2) في عملية متعددة الخيوط. يعيد PTRACE_GETEVENTMSG حالة الخروج. يمكن فحص المسجلات (على عكس ما يحدث عند الخروج "الحقيقي"). المُتتبَّع لا يزال حيًا؛ ويجب استئنافه عبر PTRACE_CONT أو فصله بـ PTRACE_DETACH لإنهاء عملية الخروج.
توقف ناتج عن أمر PTRACE_INTERRUPT، أو توقف جماعي، أو توقف ptrace أولي عند إرفاق ابن جديد (فقط إذا أُرفق باستخدام PTRACE_SEIZE).
توقف أطلقت شرارته قاعدة seccomp(2) عند دخول المُتتبَّع لاستدعاء نظام عندما يضبط المتتبع خيار PTRACE_O_TRACESECCOMP. يمكن استرداد بيانات رسالة حدث seccomp (من جزء SECCOMP_RET_DATA في قاعدة مرشح seccomp) عبر PTRACE_GETEVENTMSG. تم وصف دلالات هذا التوقف بالتفصيل في قسم منفصل أدناه.

يعيد PTRACE_GETSIGINFO عند توقفات PTRACE_EVENT القيمة SIGTRAP في si_signo، مع ضبط si_code على (event<<8) | SIGTRAP.

توقفات استدعاء النظام

إذا أُعيد تشغيل المُتتبَّع بواسطة PTRACE_SYSCALL أو PTRACE_SYSEMU، يدخل المُتتبَّع في "توقف-دخول-استدعاء-النظام" قبيل الدخول في أي استدعاء نظام (والذي لن يُنفذ إذا كانت إعادة التشغيل باستخدام PTRACE_SYSEMU، بغض النظر عن أي تغيير أُجري على المسجلات في هذه النقطة أو كيفية إعادة تشغيل المُتتبَّع بعد هذا التوقف). وبغض النظر عن الطريقة التي تسببت في توقف-دخول-استدعاء-النظام، إذا أعاد المتتبع تشغيل المُتتبَّع باستخدام PTRACE_SYSCALL، يدخل المُتتبَّع في "توقف-خروج-استدعاء-النظام" عند انتهاء استدعاء النظام، أو إذا قاطعته إشارة. (أي أن توقف-تسليم-الإشارة لا يحدث أبدًا بين توقف-دخول-استدعاء-النظام وتوقف-خروج-استدعاء-النظام؛ بل يحدث بعد توقف-خروج-استدعاء-النظام). إذا استُؤنف المُتتبَّع باستخدام أي طريقة أخرى (بما في ذلك PTRACE_SYSEMU)، فلن يحدث توقف-خروج-استدعاء-النظام. لاحظ أن جميع ذكر PTRACE_SYSEMU ينطبق بالتساوي على PTRACE_SYSEMU_SINGLESTEP.

ومع ذلك، حتى لو استُؤنف المُتتبَّع باستخدام PTRACE_SYSCALL، فليس مضمونًا أن يكون التوقف التالي هو توقف-خروج-استدعاء-النظام. الاحتمالات الأخرى هي أن يتوقف المُتتبَّع في توقف PTRACE_EVENT (بما في ذلك توقفات seccomp)، أو يخرج (إذا دخل _exit(2) أو exit_group(2))، أو يُقتل بواسطة SIGKILL، أو يموت بصمت (إذا كان قائدًا لمجموعة خيوط، وحدث execve(2) في خيط آخر، وهذا الخيط لا يتتبعه نفس المتتبع؛ ستُناقش هذه الحالة لاحقًا).

يُلاحظ المتتبع توقف-دخول-استدعاء-النظام وتوقف-خروج-استدعاء-النظام عندما يعيد waitpid(2) القيمة WIFSTOPPED(status) صحيحة، ويعطي WSTOPSIG(status) القيمة SIGTRAP. إذا ضُبط خيار PTRACE_O_TRACESYSGOOD بواسطة المتتبع، فسيعطي WSTOPSIG(status) القيمة (SIGTRAP | 0x80).

يمكن تمييز توقفات استدعاء النظام عن توقف-تسليم-الإشارة مع SIGTRAP عبر الاستعلام بـ PTRACE_GETSIGINFO للحالات التالية:

سُلّمت SIGTRAP نتيجة لإجراء في مساحة المستخدم، على سبيل المثال، استدعاء نظام (tgkill(2)، kill(2)، sigqueue(3)، إلخ)، أو انتهاء صلاحية مؤقت POSIX، أو تغيير حالة في طابور رسائل POSIX، أو اكتمال عملية إدخال/إخراج غير متزامنة.
أُرسلت SIGTRAP بواسطة النواة.
هذا توقف استدعاء نظام.

ومع ذلك، تحدث توقفات استدعاء النظام بشكل متكرر للغاية (مرتين لكل استدعاء نظام)، وقد يكون تنفيذ PTRACE_GETSIGINFO لكل توقف مكلفًا بعض الشيء.

تسمح بعض المعماريات بتمييز الحالات من خلال فحص المسجلات. على سبيل المثال، في x86، تكون القيمة rax == -ENOSYS في توقف-دخول-استدعاء-النظام. بما أن SIGTRAP (مثل أي إشارة أخرى) تحدث دائمًا بعد توقف-خروج-استدعاء-النظام، وفي هذه النقطة نادرًا ما تحتوي rax على -ENOSYS، فإن SIGTRAP تبدو كأنها "توقف استدعاء نظام ليس توقف-دخول-استدعاء-النظام"؛ بمعنى آخر، تبدو كأنها "توقف-خروج-استدعاء-نظام شارد" ويمكن اكتشافها بهذه الطريقة. لكن هذا الاكتشاف هش ويُفضل تجنبه.

استخدام خيار PTRACE_O_TRACESYSGOOD هو الطريقة الموصى بها لتمييز توقفات استدعاء النظام عن الأنواع الأخرى من توقفات ptrace، لأنها موثوقة ولا تسبب ضعفًا في الأداء.

لا يمكن للمتتبع التمييز بين توقف-دخول-استدعاء-النظام وتوقف-خروج-استدعاء-النظام. يحتاج المتتبع إلى تتبع تسلسل توقفات ptrace لتجنب تفسير توقف الدخول على أنه توقف خروج أو العكس. بشكل عام، يتبع توقف-دخول-استدعاء-النظام دائمًا توقف-خروج-استدعاء-النظام، أو توقف PTRACE_EVENT، أو موت المُتتبَّع؛ ولا يمكن لأي أنواع أخرى من توقف ptrace أن تحدث بينهما. ومع ذلك، لاحظ أن توقفات seccomp (انظر أدناه) قد تسبب توقفات-خروج-استدعاء-النظام، دون توقفات دخول سابقة. إذا كان seccomp قيد الاستخدام، فيجب توخي الحذر لعدم تفسير مثل هذه التوقفات على أنها توقفات-دخول-استدعاء-نظام.

إذا استخدم المتتبع، بعد توقف-دخول-استدعاء-النظام، أمر إعادة تشغيل غير PTRACE_SYSCALL، فلن يُولَّد توقف-خروج-استدعاء-النظام.

يعيد PTRACE_GETSIGINFO عند توقفات استدعاء النظام القيمة SIGTRAP في si_signo، مع ضبط si_code على SIGTRAP أو (SIGTRAP|0x80).

توقفات PTRACE_EVENT_SECCOMP (لينكس 3.5 إلى لينكس 4.7)

تغير سلوك توقفات PTRACE_EVENT_SECCOMP وتفاعلها مع الأنواع الأخرى من توقفات ptrace بين إصدارات النواة. هذا يوثق السلوك من وقت تقديمها حتى لينكس 4.7 (شاملة). أما السلوك في إصدارات النواة اللاحقة فهو موثق في القسم التالي.

يحدث توقف PTRACE_EVENT_SECCOMP كلما أُطلقت قاعدة SECCOMP_RET_TRACE. هذا مستقل عن الطريقة المستخدمة لإعادة تشغيل استدعاء النظام. والجدير بالذكر أن seccomp لا يزال يعمل حتى لو أُعيد تشغيل المُتتبَّع باستخدام PTRACE_SYSEMU ويتم تخطي استدعاء النظام هذا دون قيد أو شرط.

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

توقفات PTRACE_EVENT_SECCOMP (منذ لينكس 4.8)

بدءًا من لينكس 4.8، أُعيد ترتيب توقف PTRACE_EVENT_SECCOMP ليحدث بين توقف-دخول-استدعاء-النظام وتوقف-خروج-استدعاء-النظام. لاحظ أن seccomp لم يعد يعمل (ولن يتم الإبلاغ عن PTRACE_EVENT_SECCOMP) إذا تم تخطي استدعاء النظام بسبب PTRACE_SYSEMU.

من الناحية الوظيفية، يعمل توقف PTRACE_EVENT_SECCOMP بشكل مشابه لـ توقف-دخول-استدعاء-النظام (أي أن الاستمرارات باستخدام PTRACE_SYSCALL ستؤدي إلى توقفات-خروج-استدعاء-النظام، ويمكن تغيير رقم استدعاء النظام وأي مسجلات أخرى معدلة تكون مرئية لاستدعاء النظام المراد تنفيذه أيضًا). لاحظ أنه قد يكون هناك، ولكن ليس بالضرورة، توقف-دخول-استدعاء-نظام سابق.

بعد توقف PTRACE_EVENT_SECCOMP، سيُعاد تشغيل seccomp، مع عمل قاعدة SECCOMP_RET_TRACE الآن بنفس طريقة SECCOMP_RET_ALLOW. تحديدًا، هذا يعني أنه إذا لم تُعدل المسجلات أثناء توقف PTRACE_EVENT_SECCOMP، فسيتم السماح باستدعاء النظام حينها.

توقفات PTRACE_SINGLESTEP

[تفاصيل هذا النوع من التوقفات لم تُوثق بعد.]

أوامر ptrace المعلوماتية وأوامر إعادة التشغيل

تتطلب معظم أوامر ptrace (الكل باستثناء PTRACE_ATTACH، و PTRACE_SEIZE، و PTRACE_TRACEME، و PTRACE_INTERRUPT، و PTRACE_KILL) أن يكون المُتتبَّع في حالة توقف-ptrace، وإلا فإنها تفشل مع الخطأ ESRCH.

عندما يكون المُتتبَّع في توقف-ptrace، يمكن للمتتبع قراءة وكتابة البيانات إليه باستخدام الأوامر المعلوماتية. تترك هذه الأوامر المُتتبَّع في حالة توقف-ptrace:


ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0);
ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, pid, addr, long_val);
ptrace(PTRACE_GETREGS/GETFPREGS, pid, 0, &struct);
ptrace(PTRACE_SETREGS/SETFPREGS, pid, 0, &struct);
ptrace(PTRACE_GETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_SETREGSET, pid, NT_foo, &iov);
ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo);
ptrace(PTRACE_SETSIGINFO, pid, 0, &siginfo);
ptrace(PTRACE_GETEVENTMSG, pid, 0, &long_var);
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);

لاحظ أن بعض الأخطاء لا يتم الإبلاغ عنها. على سبيل المثال، قد لا يكون لضبط معلومات الإشارة (siginfo) أي تأثير في بعض توقفات ptrace، ومع ذلك قد ينجح الاستدعاء (يعيد 0 ولا يضبط errno)؛ كما أن الاستعلام عن PTRACE_GETEVENTMSG قد ينجح ويعيد قيمة عشوائية إذا لم يكن توقف ptrace الحالي موثقًا كأنه يعيد رسالة حدث ذات مغزى.

الاستدعاء


ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);

يؤثر على مُتتبَّع واحد. تُستبدل الأعلام الحالية للمُتتبَّع. تُورَّث الأعلام بواسطة المُتتبَّعين الجدد المنشئين والذين أُرفقوا آليًا عبر خيارات PTRACE_O_TRACEFORK، أو PTRACE_O_TRACEVFORK، أو PTRACE_O_TRACECLONE النشطة.

مجموعة أخرى من الأوامر تجعل المُتتبَّع المتوقف بـ ptrace يعمل. لها الشكل التالي:


ptrace(cmd, pid, 0, sig);

حيث cmd هو PTRACE_CONT، أو PTRACE_LISTEN، أو PTRACE_DETACH، أو PTRACE_SYSCALL، أو PTRACE_SINGLESTEP، أو PTRACE_SYSEMU، أو PTRACE_SYSEMU_SINGLESTEP. إذا كان المُتتبَّع في توقف-تسليم-الإشارة، فإن sig هي الإشارة المراد حقنها (إذا كانت غير صفرية). وإلا، فقد يتم تجاهل sig. (عند إعادة تشغيل مُتتبَّع من توقف ptrace غير توقف-تسليم-الإشارة، فإن الممارسة الموصى بها هي تمرير 0 دائمًا في sig).

الإرفاق والفصل

يمكن إرفاق خيط بالمتتبع باستخدام الاستدعاء


ptrace(PTRACE_ATTACH, pid, 0, 0);

أو


ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_flags);

يرسل PTRACE_ATTACH إشارة SIGSTOP إلى هذا الخيط. إذا أراد المتتبع ألا يكون لهذه SIGSTOP أي تأثير، فيجب عليه كبتها. لاحظ أنه إذا أُرسلت إشارات أخرى بشكل متزامن إلى هذا الخيط أثناء الإرفاق، فقد يرى المتتبع أن المُتتبَّع يدخل في توقف-تسليم-الإشارة مع إشارة (إشارات) أخرى أولاً! الممارسة المعتادة هي إعادة حقن هذه الإشارات حتى تُرى SIGSTOP، ثم كبت حقن SIGSTOP. الخلل التصميمي هنا هو أن إرفاق ptrace وإشارة SIGSTOP المسلمة بشكل متزامن قد يتسابقان وقد تضيع SIGSTOP المتزامنة.

بما أن الإرفاق يرسل SIGSTOP والمتتبع يكبتها عادة، فقد يتسبب ذلك في عودة EINTR شاردة من استدعاء النظام الذي يتم تنفيذه حاليًا في المُتتبَّع، كما هو موضح في قسم "حقن الإشارة وكبتها".

منذ لينكس 3.4، يمكن استخدام PTRACE_SEIZE بدلاً من PTRACE_ATTACH. لا يوقف PTRACE_SEIZE العملية المرفقة. إذا كنت بحاجة إلى إيقافها بعد الإرفاق (أو في أي وقت آخر) دون إرسال أي إشارات إليها، فاستخدم أمر PTRACE_INTERRUPT.

العملية


ptrace(PTRACE_TRACEME, 0, 0, 0);

تحول الخيط المستدعي إلى مُتتبَّع. يستمر الخيط في العمل (لا يدخل في توقف-ptrace). الممارسة الشائعة هي اتباع PTRACE_TRACEME بـ


raise(SIGSTOP);

والسماح للأب (الذي أصبح متتبعنا الآن) بمراقبة توقف-تسليم-الإشارة الخاص بنا.

إذا كانت خيارات PTRACE_O_TRACEFORK، أو PTRACE_O_TRACEVFORK، أو PTRACE_O_TRACECLONE سارية المفعول، فإن الأبناء الذين أُنشئوا بواسطة، على التوالي، vfork(2) أو clone(2) مع علم CLONE_VFORK، و fork(2) أو clone(2) مع ضبط إشارة الخروج على SIGCHLD، وأنواع أخرى من clone(2)، يتم إرفاقهم آليًا بنفس المتتبع الذي تتبع والدهم. تُسلم SIGSTOP للأبناء، مما يجعلهم يدخلون في توقف-تسليم-الإشارة بعد خروجهم من استدعاء النظام الذي أنشأهم.

يتم فصل المُتتبَّع بواسطة:


ptrace(PTRACE_DETACH, pid, 0, sig);

PTRACE_DETACH هي عملية إعادة تشغيل؛ لذا فهي تتطلب أن يكون المُتتبَّع في توقف-ptrace. إذا كان المُتتبَّع في توقف-تسليم-الإشارة، فيمكن حقن إشارة. وإلا، فقد يتم تجاهل معامل sig بصمت.

إذا كان المُتتبَّع يعمل عندما يريد المتتبع فصله، فإن الحل المعتاد هو إرسال SIGSTOP (باستخدام tgkill(2)، للتأكد من وصولها إلى الخيط الصحيح)، وانتظار المُتتبَّع حتى يتوقف في توقف-تسليم-الإشارة لـ SIGSTOP ثم فصله (مع كبت حقن SIGSTOP). ثمة خلل تصميمي هو أن هذا يمكن أن يتسابق مع إشارات SIGSTOP المتزامنة. تعقيد آخر هو أن المُتتبَّع قد يدخل في توقفات ptrace أخرى ويحتاج إلى إعادة تشغيله وانتظاره مرة أخرى حتى تُرى SIGSTOP. تعقيد إضافي هو التأكد من أن المُتتبَّع ليس متوقفًا بالفعل بـ ptrace، لأنه لا يحدث تسليم إشارات أثناء توقفه - ولا حتى SIGSTOP.

إذا مات المتتبع، يتم فصل جميع المُتتبَّعين آليًا وإعادة تشغيلهم، ما لم يكونوا في حالة توقف جماعي. معالجة إعادة التشغيل من التوقف الجماعي تحتوي حاليًا على أخطاء، ولكن السلوك "كما هو مخطط له" هو ترك المُتتبَّع متوقفًا وينتظر SIGCONT. إذا أُعيد تشغيل المُتتبَّع من توقف-تسليم-الإشارة، فسيتم حقن الإشارة المعلقة.

execve(2) تحت ptrace

عندما يستدعي خيط واحد في عملية متعددة الخيوط execve(2)، تدمر النواة جميع الخيوط الأخرى في العملية، وتعيد ضبط معرف الخيط للخيط المنفذ إلى معرف مجموعة الخيوط (معرف العملية). (أو، بعبارة أخرى، عندما تقوم عملية متعددة الخيوط بـ execve(2)، عند اكتمال الاستدعاء، يبدو الأمر كما لو أن execve(2) حدثت في قائد مجموعة الخيوط، بغض النظر عن الخيط الذي قام بـ execve(2)). إعادة ضبط معرف الخيط هذه تبدو مربكة للغاية للمتتبعين:

تتوقف جميع الخيوط الأخرى في توقف PTRACE_EVENT_EXIT، إذا كان خيار PTRACE_O_TRACEEXIT مفعلًا. ثم تبلغ جميع الخيوط الأخرى باستثناء قائد مجموعة الخيوط عن الموت كما لو أنها خرجت عبر _exit(2) مع رمز الخروج 0.
يغير المُتتبَّع المنفذ معرف خيطه أثناء وجوده في execve(2). (تذكر أنه تحت ptrace، فإن "pid" العائد من waitpid(2)، أو الذي يتم تغذيته في استدعاءات ptrace، هو معرف خيط المُتتبَّع). أي أنه يتم إعادة ضبط معرف خيط المُتتبَّع ليكون هو نفسه معرف عمليته، وهو نفسه معرف خيط قائد مجموعة الخيوط.
ثم يحدث توقف PTRACE_EVENT_EXEC، إذا كان خيار PTRACE_O_TRACEEXEC مفعلًا.
إذا كان قائد مجموعة الخيوط قد أبلغ عن توقف PTRACE_EVENT_EXIT الخاص به بحلول هذا الوقت، فسيظهر للمتتبِّع أن قائد الخيوط الميت "يظهر مجددًا من العدم". (ملاحظة: لا يبلغ قائد مجموعة الخيوط عن موته عبر WIFEXITED(status) إلا بعد أن يكون هناك خيط حي واحد آخر على الأقل؛ وهذا يلغي احتمالية أن يراه المتتبِّع يموت ثم يظهر مجددًا). وإذا كان قائد مجموعة الخيوط لا يزال حيًا، فقد يبدو للمتتبِّع كما لو أن قائد مجموعة الخيوط عاد من استدعاء نظام مختلف عن الذي دخله، أو حتى "عاد من استدعاء نظام على الرغم من أنه لم يكن في أي استدعاء نظام". أما إذا كان قائد مجموعة الخيوط غير متتبَّع (أو كان يتتبعه متتبِّع مختلف)، فإنه أثناء execve(2) سيظهر كما لو أنه أصبح متتبَّعًا لمتتبِّع الخيط المتتبَّع الذي ينفذ عملية التنفيذ (execing tracee).

كل الآثار المذكورة أعلاه هي نتاج لتغير معرف الخيط في المُتتبَّع.

خيار PTRACE_O_TRACEEXEC هو الأداة الموصى بها للتعامل مع هذا الموقف. أولاً، يفعل توقف PTRACE_EVENT_EXEC، الذي يحدث قبل عودة execve(2). في هذا التوقف، يمكن للمتتبع استخدام PTRACE_GETEVENTMSG لاسترداد معرف الخيط السابق للمُتتبَّع. (قُدمت هذه الميزة في لينكس 3.0). ثانيًا، يعطل خيار PTRACE_O_TRACEEXEC توليد إشارة SIGTRAP التقليدية عند execve(2).

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

عند تلقي إشعار توقف PTRACE_EVENT_EXEC، يجب على المتتبع تنظيف جميع هياكل بياناته الداخلية التي تصف خيوط هذه العملية، والاحتفاظ بهيكل بيانات واحد فقط - ذلك الذي يصف المُتتبَّع الوحيد الذي لا يزال يعمل، مع


معرف الخيط == معرف مجموعة الخيوط == معرف العملية.

مثال: استدعاء خيطين لـ execve(2) في نفس الوقت:

*** حصلنا على توقف-دخول-استدعاء-نظام في الخيط 1: **
PID1 execve("/bin/foo", "foo" <unfinished ...>
*** أصدرنا PTRACE_SYSCALL للخيط 1 **
*** حصلنا على توقف-دخول-استدعاء-نظام في الخيط 2: **
PID2 execve("/bin/bar", "bar" <unfinished ...>
*** أصدرنا PTRACE_SYSCALL للخيط 2 **
*** حصلنا على PTRACE_EVENT_EXEC لـ PID0، أصدرنا PTRACE_SYSCALL **
*** حصلنا على توقف-خروج-استدعاء-نظام لـ PID0: **
PID0 <... execve resumed> )             = 0

إذا كان خيار PTRACE_O_TRACEEXEC ليس ساري المفعول للمُتتبَّع المنفذ، وإذا كان المُتتبَّع قد أُرفق عبر PTRACE_ATTACH بدلاً من PTRACE_SEIZE، فإن النواة تسلم إشارة SIGTRAP إضافية للمُتتبَّع بعد عودة execve(2). هذه إشارة عادية (مشابهة لتلك التي يمكن توليدها بواسطة kill -TRAP)، وليست نوعًا خاصًا من توقف ptrace. استخدام PTRACE_GETSIGINFO لهذه الإشارة يعيد si_code مضبوطًا على 0 (SI_USER). قد يتم حجب هذه الإشارة بواسطة قناع الإشارة، وبالتالي قد يتم تسليمها لاحقًا (بكثير).

عادة، لا يرغب المتتبع (على سبيل المثال، strace(1)) في إظهار إشارة SIGTRAP الإضافية هذه لما بعد execve للمستخدم، وسيكبت تسليمها للمُتتبَّع (إذا كانت SIGTRAP مضبوطة على SIG_DFL، فهي إشارة قاتلة). ومع ذلك، فإن تحديد أي SIGTRAP يجب كبتها ليس بالأمر السهل. ضبط خيار PTRACE_O_TRACEEXEC أو استخدام PTRACE_SEIZE وبالتالي كبت هذه الـ SIGTRAP الإضافية هو النهج الموصى به.

الأب الحقيقي

تسيء واجهة برمجة تطبيقات ptrace استخدام نظام إشارات الأب/الابن القياسي في يونكس عبر waitpid(2). كان هذا يتسبب في توقف الأب الحقيقي للعملية عن تلقي عدة أنواع من إشعارات waitpid(2) عندما يتم تتبع العملية الابنة بواسطة عملية أخرى.

أُصلحت العديد من هذه الأخطاء، ولكن اعتبارًا من لينكس 2.6.38 لا يزال هناك عدة أخطاء قائمة؛ انظر قسم الأخطاء (BUGS) أدناه.

اعتبارًا من لينكس 2.6.38، يُعتقد أن ما يلي يعمل بشكل صحيح:

يتم الإبلاغ عن الخروج/الموت بسبب إشارة أولاً للمتتبع، ثم عندما يستهلك المتتبع نتيجة waitpid(2)، يُبلغ الأب الحقيقي (للأب الحقيقي فقط عندما تخرج العملية متعددة الخيوط بالكامل). إذا كان المتتبع والأب الحقيقي هما نفس العملية، فيُرسل التقرير مرة واحدة فقط.

قيمة الإرجاع

عند النجاح، تعيد عمليات PTRACE_PEEK* البيانات المطلوبة (ولكن انظر الملاحظات)، وتعيد عملية PTRACE_SECCOMP_GET_FILTER عدد التعليمات في برنامج BPF، وتعيد عملية PTRACE_GET_SYSCALL_INFO عدد البايتات المتاحة لتتم كتابتها بواسطة النواة، والعمليات الأخرى تعيد صفرًا.

عند حدوث خطأ، تعيد جميع العمليات -1، ويُضبط errno للإشارة إلى الخطأ. بما أن القيمة العائدة من عملية PTRACE_PEEK* ناجحة قد تكون -1، فيجب على المستدعي مسح errno قبل الاستدعاء، ثم التحقق منه بعد ذلك لتحديد ما إذا كان هناك خطأ قد وقع أم لا.

الأخطاء

(في i386 فقط) حدث خطأ أثناء تخصيص أو تحرير سجل تنقيح.
حدثت محاولة للقراءة من أو الكتابة في منطقة غير صالحة في ذاكرة المتتبِّع أو المتتبَّع، ربما لأن المنطقة لم تكن ممسوحة أو لا يمكن الوصول إليها. لسوء الحظ، في لينكس، ستُرجع التنوعات المختلفة لهذا الخطأ EIO أو EFAULT بشكل عشوائي تقريبًا.
حدثت محاولة لضبط خيار غير صالح.
op غير صالحة، أو حدثت محاولة للقراءة من أو الكتابة في منطقة غير صالحة في ذاكرة المتتبِّع أو المتتبَّع، أو حدث انتهاك لمحاذاة الكلمة، أو عُينت إشارة غير صالحة خلال عملية إعادة تشغيل.
لا يمكن تتبع العملية المحددة. قد يكون هذا بسبب امتلاك المتتبِّع امتيازات غير كافية (القدرة المطلوبة هي CAP_SYS_PTRACE)؛ لا تستطيع العمليات غير المميزة تتبع العمليات التي لا يمكنها إرسال إشارات إليها أو تلك التي تُشغل برامج set-user-ID/set-group-ID، لأسباب واضحة. بدلاً من ذلك، قد تكون العملية قيد التتبع بالفعل، أو (قبل لينكس 2.6.26) قد تكون init(1) (PID 1).
العملية المحددة غير موجودة، أو لا يتبعها المستدعِي حاليًا، أو ليست متوقفة (للعمليات التي تتطلب أن يكون المتتبَّع متوقفًا).

المعايير

لا يوجد.

التاريخ

SVr4, 4.3BSD.

قبل لينكس 2.6.26، لم يكن بالإمكان تتبع init(1)، العملية ذات المعرف PID 1.

ملاحظات

على الرغم من أن المعاملات المُمررة إلى ptrace() تُفسر وفقًا للنموذج المذكور، إلا أن glibc تعلن حاليًا عن ptrace() كدالة متغيرة المعاملات مع تثبيت معامل op فقط. يُوصى دائمًا بتوفير أربعة معاملات، حتى لو كانت العملية المطلوبة لا تستخدمها، مع ضبط المعاملات غير المستخدمة/المتجاهلة إلى 0L أو (void *) 0.

يستمر والد المتتبَّع في كونه المتتبِّع حتى لو استدعى ذلك المتتبِّع execve(2).

تخطيط محتويات الذاكرة ومنطقة USER يعتمد بشكل كبير على نظام التشغيل والمعمارية. الإزاحة المزودة، والبيانات المُرجعة، قد لا تتطابق تمامًا مع تعريف struct user.

حجم "الكلمة" يُحدد حسب نوع نظام التشغيل (على سبيل المثال، في لينكس 32-بت يكون 32 بت).

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

التحقق من وضع الوصول لـ Ptrace

تتطلب أجزاء مختلفة من واجهة برمجة تطبيقات فضاء المستخدم-النواة (وليس فقط عمليات ptrace()) ما يسمى بفحوصات "وضع الوصول لـ ptrace"، التي تحدد نتيجتها ما إذا كانت العملية مسموحًا بها (أو، في حالات قليلة، تجعل عملية "القراءة" تُرجع بيانات مطهرة). تُجرى هذه الفحوصات في الحالات التي يمكن فيها لعملية واحدة فحص معلومات حساسة عن عملية أخرى، أو في بعض الحالات تعديل حالتها. تعتمد الفحوصات على عوامل مثل أوراق الاعتماد والقدرات للعمليتين، وما إذا كانت العملية "الهدف" قابلة للتفريغ أم لا، ونتائج الفحوصات التي تُجريها أي وحدة أمن لينكس (LSM) مفعّلة —على سبيل المثال، SELinux أو Yama أو Smack— وبواسطة وحدة commoncap LSM (التي تُستدعى دائمًا).

قبل لينكس 2.6.27، كانت جميع فحوصات الوصول من نوع واحد. منذ لينكس 2.6.27، صُنّف مستويان لوضع الوصول:

لعمليات "القراءة" أو العمليات الأخرى الأقل خطورة، مثل: get_robust_list(2)؛ و kcmp(2)؛ وقراءة /proc/pid/auxv، أو /proc/pid/environ، أو /proc/pid/stat؛ أو readlink(2) لملف /proc/pid/ns/*.
لعمليات "الكتابة"، أو العمليات الأخرى الأكثر خطورة، مثل: إرفاق ptrace (PTRACE_ATTACH) بعملية أخرى أو استدعاء process_vm_writev(2). (كان PTRACE_MODE_ATTACH فعليًا هو المبدئي قبل لينكس 2.6.27.)

منذ لينكس 4.5، تدمج فحوصات وضع الوصول المذكورة أعلاه (عبر معامل OR) مع أحد المعدلات التالية:

استخدام UID و GID لنظام ملفات المستدعِي (انظر credentials(7)) أو القدرات الفعالة لفحوصات وحدة LSM.
استخدام UID و GID الحقيقيين للمستدعِي أو القدرات المسموح بها لفحوصات وحدة LSM. كان هذا فعليًا هو المبدئي قبل لينكس 4.5.

نظرًا لأن دمج أحد معدلات أوراق الاعتماد مع أحد أوضاع الوصول المذكورة آنفًا هو أمر شائع، فقد عُرفت بعض الماكرو في مصادر النواة لهذه التوليفات:

عُرف كـ PTRACE_MODE_READ | PTRACE_MODE_FSCREDS.
عُرف كـ PTRACE_MODE_READ | PTRACE_MODE_REALCREDS.
عُرف كـ PTRACE_MODE_ATTACH | PTRACE_MODE_FSCREDS.
عُرف كـ PTRACE_MODE_ATTACH | PTRACE_MODE_REALCREDS.

يمكن دمج معدل إضافي واحد (عبر OR) مع وضع الوصول:

عدم تدقيق فحص وضع الوصول هذا. يُستخدم هذا المعدل لفحوصات وضع وصول ptrace (مثل الفحوصات عند قراءة /proc/pid/stat) التي تؤدي مجرد تصفية المخرجات أو تطهيرها، بدلاً من التسبب في إرجاع خطأ إلى المستدعِي. في هذه الحالات، لا يعد الوصول إلى الملف انتهاكًا أمنيًا ولا يوجد سبب لإنشاء سجل تدقيق أمني. يقمع هذا المعدل إنشاء سجل تدقيق كهذا لفحص وصول معين.

لاحظ أن جميع ثوابت PTRACE_MODE_* الموصوفة في هذا القسم الفرعي هي ثوابت داخلية للنواة، وغير مرئية لفضاء المستخدم. ذُكرت أسماء الثوابت هنا من أجل تسمية الأنواع المختلفة لفحوصات وضع وصول ptrace التي تُجرى لاستدعاءات النظام المختلفة والوصول إلى الملفات الوهمية المختلفة (مثل الموجودة تحت /proc). تُستخدم هذه الأسماء في صفحات الدليل الأخرى لتوفير اختصار بسيط لتسمية فحوصات النواة المختلفة.

الخوارزمية المستخدمة للتحقق من وضع وصول ptrace تحدد ما إذا كان مسموحًا للعملية المستدعية إجراء الإجراء المقابل على العملية الهدف. (في حالة فتح ملفات /proc/pid، فإن "العملية المستدعية" هي التي تفتح الملف، والعملية ذات المعرف PID المقابل هي "العملية الهدف".) الخوارزمية هي كما يلي:

(1)
إذا كان خيط الاستدعاء والخيط الهدف في نفس مجموعة الخيوط، فإن الوصول مسموح به دائمًا.
(2)
إذا حدد وضع الوصول PTRACE_MODE_FSCREDS، فاستخدم للفحص في الخطوة التالية UID و GID لنظام ملفات المستدعِي. (كما لوحظ في credentials(7)، فإن UID و GID لنظام الملفات لهما نفس القيم تقريبًا للمعرفات الفعالة المقابلة.)
خلاف ذلك، يحدد وضع الوصول PTRACE_MODE_REALCREDS، لذا استخدم UID و GID الحقيقيين للمستدعِي للفحوصات في الخطوة التالية. (معظم واجهات برمجة التطبيقات التي تفحص UID و GID للمستدعِي تستخدم المعرفات الفعالة. لأسباب تاريخية، يستخدم فحص PTRACE_MODE_REALCREDS المعرفات الحقيقية بدلاً من ذلك.)
(3)
يُرفض الوصول إذا لم يتحقق أي مما يلي:
معرفات المستخدم الحقيقية، والفعالة، والمحفوظة للهدف تطابق معرف مستخدم المستدعِي، و معرفات المجموعة الحقيقية، والفعالة، والمحفوظة للهدف تطابق معرف مجموعة المستدعِي.
يمتلك المستدعِي قدرة CAP_SYS_PTRACE في فضاء مستخدم الهدف.
(4)
يُرفض الوصول إذا كانت سمة "قابلية التفريغ" للعملية الهدف لها قيمة غير 1 (SUID_DUMP_USER؛ انظر مناقشة PR_SET_DUMPABLE في prctl(2))، ولم يمتلك المستدعِي قدرة CAP_SYS_PTRACE في فضاء مستخدم العملية الهدف.
(5)
تُستدعى واجهة LSM للنواة security_ptrace_access_check() لمعرفة ما إذا كان وصول ptrace مسموحًا به. تعتمد النتائج على وحدات LSM. ينفذ تطبيق هذه الواجهة في commoncap LSM الخطوات التالية:
(5.1)
إذا تضمن وضع الوصول PTRACE_MODE_FSCREDS، فاستخدم مجموعة القدرات الفعالة للمستدعِي في الفحص التالي؛ وإلا (يحدد وضع الوصول PTRACE_MODE_REALCREDS، لذا) استخدم مجموعة قدرات المستدعِي المسموح بها.
(5.2)
يُرفض الوصول إذا لم يتحقق أي مما يلي:
المستدعِي والعملية الهدف في نفس فضاء المستخدم، وقدرات المستدعِي هي مجموعة شاملة لقدرات العملية الهدف المسموح بها.
يمتلك المستدعِي قدرة CAP_SYS_PTRACE في فضاء مستخدم العملية الهدف.
لاحظ أن commoncap LSM لا تفرق بين PTRACE_MODE_READ و PTRACE_MODE_ATTACH.
(6)
إذا لم يُرفض الوصول بأي من الخطوات السابقة، فإن الوصول مسموح به.

/proc/sys/kernel/yama/ptrace_scope

على الأنظمة التي ثبتت عليها وحدة Yama Linux Security Module (LSM) (أي أن النواة ضُبطت بـ CONFIG_SECURITY_YAMA)، يمكن استخدام ملف /proc/sys/kernel/yama/ptrace_scope (المتاح منذ لينكس 3.4) لتقييد القدرة على تتبع عملية باستخدام ptrace() (وبالتالي القدرة على استخدام أدوات مثل strace(1) و gdb(1)). الهدف من هذه القيود هو منع تصعيد الهجمات حيث يمكن لعملية مخترقة إرفاق ptrace لعمليات حساسة أخرى (مثل وكيل GPG أو جلسة SSH) يملكها المستخدم من أجل الحصول على أوراق اعتماد إضافية قد توجد في الذاكرة وبالتالي توسيع نطاق الهجوم.

بشكل أدق، تحد وحدة Yama LSM من نوعين من العمليات:

أي عملية تجري فحصًا لوضع وصول ptrace من نوع PTRACE_MODE_ATTACH —على سبيل المثال، ptrace() PTRACE_ATTACH. (انظر مناقشة "التحقق من وضع وصول Ptrace" أعلاه.)
ptrace() PTRACE_TRACEME.

العملية التي تمتلك قدرة CAP_SYS_PTRACE يمكنها تحديث ملف /proc/sys/kernel/yama/ptrace_scope بإحدى القيم التالية:

0 ("أذونات ptrace التقليدية")
لا توجد قيود إضافية على العمليات التي تجري فحوصات PTRACE_MODE_ATTACH (بخلاف تلك التي تفرضها commoncap ووحدات LSM الأخرى).
استخدام PTRACE_TRACEME لم يتغير.
1 ("ptrace مقيد") [القيمة المبدئية]
عند إجراء عملية تتطلب فحص PTRACE_MODE_ATTACH، يجب أن تمتلك العملية المستدعية إما قدرة CAP_SYS_PTRACE في فضاء مستخدم العملية الهدف أو يجب أن تكون لها علاقة محددة مسبقًا بالعملية الهدف. بشكل مبدئي، العلاقة المحددة مسبقًا هي أن تكون العملية الهدف من نسل المستدعِي.
يمكن للعملية الهدف استخدام عملية prctl(2) PR_SET_PTRACER للتصريح عن معرف PID إضافي مسموح له بإجراء عمليات PTRACE_MODE_ATTACH على الهدف. انظر ملف مصدر النواة Documentation/admin-guide/LSM/Yama.rst لمزيد من التفاصيل.
استخدام PTRACE_TRACEME لم يتغير.
2 ("إرفاق للمدير فقط")
العمليات التي تمتلك قدرة CAP_SYS_PTRACE في فضاء مستخدم العملية الهدف فقط هي التي يمكنها إجراء عمليات PTRACE_MODE_ATTACH أو تتبع الأبناء الذين يستخدمون PTRACE_TRACEME.
3 ("لا إرفاق")
لا يمكن لأي عملية إجراء عمليات PTRACE_MODE_ATTACH أو تتبع الأبناء الذين يستخدمون PTRACE_TRACEME.
بمجرد كتابة هذه القيمة في الملف، لا يمكن تغييرها.

فيما يتعلق بالقيمتين 1 و 2، لاحظ أن إنشاء فضاء مستخدم جديد يزيل فعليًا الحماية التي توفرها Yama. هذا لأن العملية في فضاء مستخدم الوالد التي يطابق معرف UID الفعال لها معرف UID لمنشئ فضاء الأبناء تمتلك جميع القدرات (بما في ذلك CAP_SYS_PTRACE) عند إجراء العمليات داخل فضاء مستخدم الأبناء (والنسل الأبعد لهذا الفضاء). بناءً على ذلك، عندما تحاول عملية ما استخدام فضاءات المستخدم لعزل نفسها، فإنها تضعف دون قصد الحماية التي توفرها وحدة Yama LSM.

الاختلافات بين مكتبة C والنواة

على مستوى استدعاء النظام، تمتلك عمليات PTRACE_PEEKTEXT و PTRACE_PEEKDATA و PTRACE_PEEKUSER واجهة برمجة تطبيقات مختلفة: فهي تخزن النتيجة في العنوان الموضح بواسطة معامل data، وتكون القيمة المُرجعة هي راية الخطأ. توفر دالة تغليف glibc واجهة برمجة التطبيقات المذكورة في قسم "الوصف" أعلاه، حيث تُرجع النتيجة عبر القيمة المُرجعة للدالة.

العلل

على المضيفين الذين لديهم ترويسات نواة لينكس 2.6، يُصرح عن PTRACE_SETOPTIONS بقيمة مختلفة عن القيمة المستخدمة في لينكس 2.4. يؤدي هذا إلى فشل التطبيقات المجمعة بترويسات نواة لينكس 2.6 عند تشغيلها على لينكس 2.4. يمكن الالتفاف على هذا بإعادة تعريف PTRACE_SETOPTIONS إلى PTRACE_OLDSETOPTIONS، إذا كان ذلك معرفًا.

تُرسل إشعارات توقف المجموعة إلى المتتبِّع، ولكن ليس إلى الوالد الحقيقي. آخر تأكيد كان في 2.6.38.6.

إذا كان قائد مجموعة الخيوط متبوعًا وخرج باستدعاء _exit(2)، سيحدث توقف PTRACE_EVENT_EXIT له (إذا طُلب ذلك)، ولكن إشعار WIFEXITED اللاحق لن يُسلم حتى تخرج جميع الخيوط الأخرى. كما شُرح أعلاه، إذا استدعى أحد الخيوط الأخرى execve(2)، فإن موت قائد مجموعة الخيوط لن يُبلغ عنه أبدًا. إذا لم يكن الخيط الذي نُفذ متبوعًا بواسطة هذا المتتبِّع، فلن يعرف المتتبِّع أبدًا أن execve(2) قد حدث. أحد الحلول الممكنة هو إجراء PTRACE_DETACH لقائد مجموعة الخيوط بدلاً من إعادة تشغيله في هذه الحالة. آخر تأكيد كان في 2.6.38.6.

قد تظل إشارة SIGKILL تسبب توقف PTRACE_EVENT_EXIT قبل الموت الفعلي بالإشارة. قد يتغير هذا في المستقبل؛ الغرض من SIGKILL هو قتل المهام دائمًا وفورًا حتى تحت ptrace. آخر تأكيد كان في لينكس 3.13.

تُرجع بعض استدعاءات النظام EINTR إذا أُرسلت إشارة إلى المتتبَّع، ولكن قُمع تسليمها بواسطة المتتبِّع. (هذه عملية شائعة جدًا: يقوم بها المنقحون عادةً عند كل إرفاق، لكي لا يقدموا SIGSTOP زائفة). اعتبارًا من لينكس 3.2.9، تأثرت استدعاءات النظام التالية (هذه القائمة من المرجح أنها غير مكتملة): epoll_wait(2)، و read(2) من واصف ملف inotify(7). العرض المعتاد لهذه العلة هو أنه عندما ترفق عملية ساكنة بالأمر


strace -p <process-ID>

عندها، وبدلاً من المخرجات المعتادة والمتوقعة في سطر واحد مثل


restart_syscall(<... استئناف الاستدعاء المقاطع ...>_

أو


select(6, [5], NULL, [5], NULL_

('‏_' تشير إلى موضع المؤشر)، تلاحظ أكثر من سطر واحد. على سبيل المثال:



clock_gettime(CLOCK_MONOTONIC, {15370, 690928118}) = 0
epoll_wait(4,_

ما لا يظهر هنا هو أن العملية كانت محجوزة في epoll_wait(2) قبل أن يُربط strace(1) بها. تسبب الربط في عودة epoll_wait(2) إلى مساحة المستخدم مع الخطأ EINTR. في هذه الحالة المعينة، استجاب البرنامج للخطأ EINTR بالتحقق من الوقت الحالي، ثم تنفيذ epoll_wait(2) مرة أخرى. (البرامج التي لا تتوقع أخطاء EINTR «الشاردة» هذه قد تتصرف بطريقة غير مقصودة عند ربط strace(1) بها.)

على عكس القواعد العادية، يمكن لغلاف glibc لتابع ptrace() أن يضبط errno على صفر.

انظر أيضًا

gdb(1)، ltrace(1)، strace(1)، clone(2)، execve(2)، fork(2)، gettid(2)، prctl(2)، seccomp(2)، sigaction(2)، tgkill(2)، vfork(2)، waitpid(2)، exec(3)، capabilities(7)، signal(7)

ترجمة

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

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

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

10 فبراير 2026 صفحات دليل لينكس 6.18