تصحيح أخطاء نشر البرامج باستخدام strace

تصحيح أخطاء نشر البرامج باستخدام strace

وظيفتي الرئيسية، في معظمها، هي نشر أنظمة البرمجيات، مما يعني أنني أقضي الكثير من الوقت في محاولة الإجابة على أسئلة مثل هذه:

  • المطور لديه هذا البرنامج يعمل، ولكن أنا لا. لماذا؟
  • بالأمس كان هذا البرنامج يعمل معي، لكنه اليوم لا يعمل. لماذا؟

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

لذلك بدلاً من أدوات التصحيح المعتادة مثل جدب لدي مجموعة أخرى من الأدوات لتصحيح أخطاء النشر. وأداتي المفضلة للتعامل مع مشكلة مثل "لماذا لا يعمل هذا البرنامج معي؟" مُسَمًّى عنيد.

ما هي الدعامة؟

عنيد هي أداة "لتتبع مكالمات النظام". تم إنشاؤه في الأصل لنظام التشغيل Linux، ولكن يمكن استخدام نفس حيل تصحيح الأخطاء مع أدوات الأنظمة الأخرى (DTrace أو كتريشن).

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

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

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

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

تصحيح أخطاء نشر البرامج باستخدام strace

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

  • وحدة الإدخال/الإخراج
  • شبكة الإدخال/الإخراج
  • الوصول إلى نظام الملفات وإدخال/إخراج الملفات
  • إدارة عمر عملية الخيط
  • إدارة الذاكرة ذات المستوى المنخفض
  • الوصول إلى برامج تشغيل أجهزة محددة

متى تستخدم الدعامة؟

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

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

سنستخدم خادمًا مستقلًا كمثال ، ولكن غالبًا ما يمكن إجراء تتبع syscall على منصات نشر أكثر تعقيدًا. تحتاج فقط إلى العثور على الأداة المناسبة.

مثال تصحيح بسيط

لنفترض أنك تريد تشغيل تطبيق الخادم الرائع foo، وينتهي بك الأمر بما يلي:

$ foo
Error opening configuration file: No such file or directory

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

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

إنتاج عنيد قد يبدو الأمر زائدًا عن الحاجة، لكن الخبر السار هو أنه يمكن تجاهل معظمه بأمان. غالبًا ما يكون من المفيد استخدام عامل التشغيل -o لحفظ نتائج التتبع في ملف منفصل:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

ما يقرب من الصفحة الأولى بأكملها من الإخراج عنيد - عادة ما يكون هذا إعدادًا منخفض المستوى للإطلاق. (مكالمات عديدة mmap, ام بروتكت, BRK لأشياء مثل اكتشاف الذاكرة منخفضة المستوى ورسم الخرائط الديناميكي للمكتبة.) في الواقع ، أثناء التصحيح ، الاستنتاجات عنيد فمن الأفضل أن تقرأ من النهاية. أدناه سيكون التحدي اكتبالذي يعرض رسالة الخطأ. ننظر أعلاه ونرى أول استدعاء للنظام الخاطئ - مكالمة openat، إعطاء خطأ كافي ("الملف أو الدليل غير موجود") يحاول فتح /etc/foo/config.json. هذا هو المكان الذي يجب أن يكون فيه ملف التكوين.

كان هذا مجرد مثال، ولكن أود أن أقول إنني أستخدم 90% من الوقت عنيد، لا شيء أكثر صعوبة ولا يجب عليك القيام به. يوجد أدناه دليل كامل لتصحيح الأخطاء خطوة بخطوة:

  • تشعر بالإحباط من رسالة خطأ system-y غامضة من أحد البرامج
  • أعد تشغيل البرنامج من عنيد
  • ابحث عن رسالة الخطأ في نتائج التتبع
  • انتقل إلى مستوى أعلى حتى تصل إلى أول مكالمة نظام فاشلة

من المحتمل جدًا أن يُظهر استدعاء النظام في الخطوة الرابعة الخطأ الذي حدث.

نصائح

قبل أن أعرض عليك مثالاً لتصحيح الأخطاء الأكثر تعقيدًا، سأعرض لك بعض الحيل للاستخدام الفعال عنيد:

الرجل هو صديقك

في العديد من أنظمة * nix ، يمكن الحصول على قائمة كاملة باستدعاءات النظام إلى kernel عن طريق التشغيل رجل يطلب النظام. سوف ترى أشياء مثل برك(2)، مما يعني أنه يمكن الحصول على مزيد من المعلومات عن طريق التشغيل رجل 2 برك.

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

استخدم -o لحفظ الإخراج في ملف

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

يستخدم -s لعرض المزيد من بيانات الوسيطة

ربما لاحظت أن النصف الثاني من رسالة الخطأ لا يظهر في مثال التتبع أعلاه. انه بسبب عنيد افتراضيًا، يُظهر فقط أول 32 بايت من وسيطة السلسلة. إذا كنت تريد رؤية المزيد، أضف شيئًا مثل -s 128 للمكالمة عنيد.

-y يجعل من السهل تتبع مآخذ الملفات، وما إلى ذلك.

"كل شيء عبارة عن ملف" يعني أن أنظمة *nix تقوم بكل عمليات الإدخال/الإخراج باستخدام واصفات الملفات، سواء كانت تنطبق بشكل خاص على ملف أو شبكة أو أنابيب بين العمليات. يعد هذا مناسبًا للبرمجة، لكنه يجعل من الصعب متابعة ما يحدث بالفعل عندما ترى شيئًا شائعًا اقرأ и اكتب في نتائج تتبع استدعاء النظام.

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

إرفاق بعملية قيد التشغيل بالفعل باستخدام -p**

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

$ strace -p 1337
...system call trace output...

قد تحتاج إلى أذونات الجذر.

استخدم -f لمشاهدة العمليات الفرعية

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

إذا كنت تعتقد أن الخطأ في عملية فرعية ، فاستخدم العبارة -f، وهذا سيمكن من تتبعها. الجانب السلبي لهذا هو أن الإخراج سوف يربكك أكثر. متى عنيد يتتبع عملية واحدة أو فرع واحد، فإنه يظهر دفق واحد من أحداث المكالمة. عندما يتتبع عمليات متعددة في وقت واحد، قد ترى بداية المكالمة التي تمت مقاطعتها بواسطة الرسالة ، ثم - مجموعة من الدعوات لفروع التنفيذ الأخرى، وعندها فقط - نهاية الأول ب <… استأنف foocall>. أو قم بتقسيم جميع نتائج التتبع إلى ملفات مختلفة باستخدام العبارة أيضًا (التفاصيل في يرشد في عنيد).

قم بتصفية التتبع باستخدام -e

كما ترى ، فإن نتيجة التتبع هي مجموعة حقيقية من جميع مكالمات النظام الممكنة. علَم -e يمكنك تصفية التتبع (انظر توجيه في عنيد). الميزة الرئيسية هي أن تشغيل التتبع الذي تمت تصفيته يكون أسرع من إجراء التتبع الكامل وبعد ذلك البقرى`في. لأكون صادقًا، أنا دائمًا تقريبًا لا أهتم.

ليست كل الأخطاء سيئة

مثال بسيط وشائع هو برنامج يبحث عن ملف في عدة أماكن في وقت واحد، مثل الصدفة التي تبحث عن دليل السلة الذي يحتوي على ملف قابل للتنفيذ:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

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

تعتبر أدلة البرمجة بلغة C جيدة لفهم مكالمات النظام.

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

مثال تصحيح أكثر تعقيدا

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

bcron - جدولة معالجة المهام، تطبيق خفي آخر *nix كرون. تم تثبيته على الخادم، ولكن عندما يحاول شخص ما تعديل الجدول، يحدث هذا:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

طيب يعني bcron حاول كتابة بعض الملفات، لكنه لم ينجح، ولن يعترف بالسبب. كشف عنيد:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

بالقرب من النهاية هناك رسالة خطأ اكتب، ولكن هذه المرة شيئا مختلفا. أولاً، لا يوجد خطأ في استدعاء النظام ذي صلة يحدث عادةً من قبل. ثانيا، من الواضح أن شخصا ما قد قرأ بالفعل رسالة الخطأ في مكان ما. يبدو أن المشكلة الحقيقية تكمن في مكان آخر، و com.bcrontab يلعب فقط الرسالة.

إذا نظرت إليها رجل 2 قراءةيمكنك أن ترى أن الوسيطة الأولى (3) هي واصف الملف الذي يستخدمه *nix لجميع عمليات معالجة الإدخال/الإخراج. كيف يمكنني معرفة ما يمثله واصف الملف 3؟ في هذه الحالة بالذات، يمكنك تشغيل عنيد مع عامل ذ (انظر أعلاه) وسيخبرك تلقائيًا، ومع ذلك، من أجل حساب مثل هذه الأشياء، من المفيد معرفة كيفية قراءة نتائج التتبع وتحليلها.

يمكن أن يكون مصدر واصف الملف أحد استدعاءات النظام العديدة (كل هذا يعتمد على ما إذا كان الواصف مخصصًا لوحدة التحكم أو مقبس الشبكة أو الملف نفسه أو أي شيء آخر)، ولكن مهما كان الأمر، فإننا نبحث عن الاستدعاءات ، إرجاع 3 (أي البحث عن "= 3" في نتائج التتبع). وفي هذه النتيجة هناك 2 منهم: openat في الأعلى و مقبس في المنتصف. openat يفتح الملف، ولكن اغلق(٣) بعد ذلك سيظهر أنه يغلق مرة أخرى. (أشعل النار: يمكن إعادة استخدام واصفات الملفات عند فتحها وإغلاقها). يتصل مقبس () يناسب لأنه الأخير من قبل اقرأ()واتضح أن bcrontab يعمل بشيء من خلال المقبس. يوضح السطر التالي أن واصف الملف مرتبط بـ مقبس مجال يونكس على طول الطريق / var / run / bcron-spool.

لذلك، نحن بحاجة إلى العثور على العملية المرتبطة بها مقبس يونكس على الجانب الآخر. هناك بعض الحيل الرائعة لهذا الغرض، وكلاهما مفيد لتصحيح أخطاء عمليات نشر الخادم. الأول هو الاستخدام نتستت أو أحدث ss (حالة المقبس). يُظهر كلا الأمرين اتصالات الشبكة النشطة للنظام ويأخذان المشغل -l لوصف مآخذ الاستماع، فضلا عن المشغل -p لعرض البرامج المتصلة بالمقبس كعميل. (هناك العديد من الخيارات المفيدة، ولكن هذين الخيارين يكفيان لهذه المهمة.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

هذا يشير إلى أن المستمع فريق com.inixserver، يعمل بمعرف العملية 20629. (وبالمصادفة ، فإنه يستخدم واصف الملف 3 كمقبس خاص به.)

الأداة الثانية المفيدة حقًا للعثور على نفس المعلومات تسمى lsof. يسرد جميع الملفات المفتوحة (أو واصفات الملفات) على النظام. أو يمكنك الحصول على معلومات حول ملف واحد محدد:

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

العملية 20629 هي خادم طويل العمر، لذا يمكنك الارتباط به عنيد مع شيء من هذا القبيل ستراس -o /tmp/trace -p 20629. إذا قمت بتحرير مهمة cron في محطة طرفية أخرى، فسنحصل على نتائج التتبع مع حدوث خطأ. وهذه هي النتيجة:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(آخر قبول () لن يكتمل عند تتبعه.) مرة أخرى، لسوء الحظ، هذه النتيجة لا تحتوي على الخطأ الذي نبحث عنه. لا نرى أي رسائل يرسلها bcrontag إلى المقبس أو يستقبله منه. بدلاً من ذلك، التحكم المستمر في العملية (استنساخ, wait4, سيجتشلد إلخ.) تولد هذه العملية عملية فرعية، والتي، كما قد تتخيل، تقوم بالعمل الحقيقي. وإذا كنت بحاجة إلى اللحاق بأثرها، أضف إلى التحدي ستراس -f. هذا ما نجده من خلال البحث عن رسالة الخطأ في النتيجة الجديدة بصرامة -f -o / tmp / trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

الآن، هذا شيء. تحصل العملية 21470 على خطأ "تم رفض الوصول" عند محاولة إنشاء ملف في المسار تمب/spool.21470.1573692319.854640 (يتعلق بدليل العمل الحالي). إذا كنا نعرف دليل العمل الحالي فحسب، فسنعرف أيضًا المسار الكامل وسنكون قادرين على معرفة سبب عدم تمكن العملية من إنشاء ملفها المؤقت هناك. لسوء الحظ، تم إنهاء العملية بالفعل، لذا لا يمكنك استخدامها فقط لوزوف-ع 21470 من أجل العثور على الدليل الحالي، ولكن يمكنك العمل في الاتجاه المعاكس - ابحث عن استدعاءات نظام PID 21470 التي تغير الدليل. (في حالة عدم وجود أي منها، يجب أن يكون PID 21470 قد ورثها من الوالد، وهذا بالفعل من خلال lsof -p لا يكتشف.) هذا النظام هو استدعاء تشدر (وهو أمر يسهل اكتشافه بمساعدة محركات بحث الويب الحديثة). وهذه هي نتيجة عمليات البحث العكسي بناءً على نتائج التتبع، حتى خادم PID 20629 نفسه:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(إذا كنت ضائعًا، فربما ينبغي عليك قراءة مشاركتي السابقة حول * إدارة العمليات والقذائف * لا شيء.) لذلك لم يحصل الخادم PID 20629 على إذن لإنشاء ملف في المسار /var/spool/cron/tmp/spool.21470.1573692319.854640. على الأرجح، السبب في ذلك هو إعدادات أذونات نظام الملفات الكلاسيكية. دعونا تحقق:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

هذا هو المكان الذي دفن فيه الكلب! يعمل الخادم كمستخدم cron، لكن الجذر فقط لديه الإذن بالكتابة إلى الدليل /فار/التخزين المؤقت/كرون/tmp/. أمر بسيط تشاون كرون /فار/التخزين المؤقت/كرون/تمب/ سيجبر bcron العمل بشكل صحيح. (إذا لم تكن هذه هي المشكلة، فإن المشتبه به التالي على الأرجح هو وحدة أمان kernel مثل SELinux أو AppArmor، لذا سأتحقق من سجل رسائل kernel باستخدام dmesg.)

في المجموع

يمكن أن تكون نتائج تتبع syscall ساحقة بالنسبة للمبتدئين، ولكنني آمل أن تكون قد أظهرت أنها طريقة سريعة لتصحيح فئة كاملة من مشكلات النشر الشائعة. تخيل أنك تحاول تصحيح أخطاء عملية متعددة bcronباستخدام مصحح الأخطاء خطوة بخطوة.

ينتج عن تحليل التتبع ترتيب عكسي على طول سلسلة استدعاء النظام يتطلب مهارة ، ولكن كما قلت ، دائمًا تقريبًا ، يتم استخدام عنيد، أنا فقط أحصل على نتيجة التتبع وأبحث عن الأخطاء بدءًا من النهاية. على أي حال، عنيد يساعدني على توفير الكثير من الوقت في التصحيح. آمل أن يكون مفيدا لك أيضا.

المصدر: www.habr.com

إضافة تعليق