
ماذا يرى مبرمج عند البدء في العمل مع لغة C؟ يرى fopen
، printf
، scanf
والعديد من الوظائف الأخرى. يرى جميع أنواع open
و mmap
- يبدو ، لماذا تسليط الضوء عليها؟ ولكن ، على عكس المجموعة الأولى ، فإن هاتين الوظيفتين عند تنفيذهما على Linux kernel هما مكالمات النظام (في الواقع لا ، لا يمكن استدعاء مكالمة النظام أبدًا ببساطة كدالة ، وبالتالي يحتوي libc
على أغلفة تقوم بإعادة حزم الوسائط وأحيانًا ، كما هو الحال مع open
، استبدال مكالمات النظام القديم بمكالمات جديدة أكثر عمومية). بشكل عام ، على عكس الآلاف من وظائف المكتبة المتوفرة على نظام جنو / لينكس النموذجي ، تحتوي واجهة kernel على عدد محدود من نقاط الدخول - من أجل عدة مئات ، ولكن بالنسبة لمساحة المستخدم ، فإنه يتعطل (على سبيل المثال ، الوصول إلى صفحة مفقودة) ، بالنسبة إلى kernel - الوضع الافتراضي للعملية.
في هذه المقالة سأخبرك ببعض الحقائق المثيرة في رأيي. لن يكون لها تفاصيل تنفيذ مملّة أخرى (على الأرجح). سيكون السبب الرئيسي وراء ردة فعلي "وماذا يمكن أن يكون كذلك؟!؟".
أولاً ، بعض التعليقات على النص قبل kat: تحتوي بعض مكالمات النظام على واجهة اختيارية في شكل دالة من كائن مشترك يسمى vDSO ، والذي يضعه kernel في العملية. هناك عدد قليل من هذه الوظائف (شيء ما حوالي أربعة ، ولكن الكمية المحددة ، على ما يبدو ، قد تعتمد على إصدار kernel والهندسة المعمارية) - كل هذه أنواع من time
و gettimeofday
، والتي ، من ناحية ، يتم استخدامها في كثير من الأحيان ، ومن ناحية أخرى ، تم تنفيذها دون تغيير سياق النواة.
ثانياً ، لا ينتهي SIGSEGV دائمًا بتعطل العملية ، لكننا سنتحدث عن ذلك userfaultfd
عندما يتعلق الأمر userfaultfd
.
تنويه: تذكر أنه باستخدام معظم الميزات المعروضة هنا ، فأنت تقوم بربط برنامج Linux الخاص بك. هذا أمر طبيعي إذا قمت بهذه الطريقة بالتحسين الاختياري لنوع معين من النظام أو ميزة إضافية لم تكن موجودة. لكن على خلاف ذلك ، أوصي بالتفكير في كيفية عمل احتياطي عبر النظام الأساسي.
أسئلة عامة
بالنسبة للمبتدئين ، كيف يمكن تصحيح كل هذا؟ بالطبع سوف يساعدنا strace
! نظرًا لأن مجموعة مكالمات النظام محدودة ، ويعرف معظم strace
"عن طريق البصر" ، فإنه لن يظهر فقط "تم تمرير المؤشر 0x12345678" ، ولكنه سيصف ما يتم نقله في هذا الاتجاه أو ذاك في هذا الهيكل. إذا كان strace
كافية ، فيمكنك باستخدام الخيار -k
أن تطلب منه إصدار مكدس للمكالمات.
يبدو شيء من هذا القبيل $ strace -k sleep 1 execve("/bin/sleep", ["sleep", "1"], 0x7ffe9f9cce30 /* 60 vars */) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(execve+0xb) [0xe601b] > /usr/bin/strace(+0x0) [0xa279c] > /usr/bin/strace(+0x0) [0xa41d2] > /usr/bin/strace(+0x0) [0x7090b] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /usr/bin/strace(+0x0) [0x7112a] brk(NULL) = 0x558936ded000 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x20b) [0x1ccdb] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1cd2) [0x1b872] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] arch_prctl(0x3001 /* ARCH_??? */, 0x7fff593c0070) = -1 EINVAL ( ) > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e25) [0x1b9c5] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] access("/etc/ld.so.preload", R_OK) = -1 ENOENT ( ) > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x10cb) [0x1db9b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c12] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1238) [0x1dd08] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x73a) [0x11d4a] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] fstat(3, {st_mode=S_IFREG|0644, st_size=254851, ...}) = 0 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1009) [0x1dad9] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x761) [0x11d71] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] mmap(NULL, 254851, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc49621c000 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1426) [0x1def6] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x79d) [0x11dad] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] close(3) = 0 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x10fb) [0x1dbcb] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_debug_state+0x780) [0x11d90] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_exception_free+0x908) [0x189c8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa362] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x1238) [0x1dd08] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x7d40] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa3a8] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360r\2\0\0\0\0\0"..., 832) = 832 > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_error+0x12f8) [0x1ddc8] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x7d79] > /lib/x86_64-linux-gnu/ld-2.30.so() [0xa3a8] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x41b5) [0xeb35] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_catch_exception+0x65) [0x1ca85] > /lib/x86_64-linux-gnu/ld-2.30.so(_dl_rtld_di_serinfo+0x4603) [0xef83] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x3c55] > /lib/x86_64-linux-gnu/ld-2.30.so(__get_cpu_features+0x1e7b) [0x1ba1b] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x203c] > /lib/x86_64-linux-gnu/ld-2.30.so() [0x1108] ... ... brk(NULL) = 0x558936ded000 > /lib/x86_64-linux-gnu/libc-2.30.so(brk+0xb) [0x11755b] > /lib/x86_64-linux-gnu/libc-2.30.so(__sbrk+0x67) [0x117617] > /lib/x86_64-linux-gnu/libc-2.30.so(__default_morecore+0xd) [0x9fd3d] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x2725) [0x9a745] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3943) [0x9b963] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3b2b) [0x9bb4b] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x4d9e) [0x9cdbe] > /lib/x86_64-linux-gnu/libc-2.30.so(textdomain+0x740) [0x3be70] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1d35) [0x35515] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] brk(0x558936e0e000) = 0x558936e0e000 > /lib/x86_64-linux-gnu/libc-2.30.so(brk+0xb) [0x11755b] > /lib/x86_64-linux-gnu/libc-2.30.so(__sbrk+0x91) [0x117641] > /lib/x86_64-linux-gnu/libc-2.30.so(__default_morecore+0xd) [0x9fd3d] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x2725) [0x9a745] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3943) [0x9b963] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x3b2b) [0x9bb4b] > /lib/x86_64-linux-gnu/libc-2.30.so(thrd_yield+0x4d9e) [0x9cdbe] > /lib/x86_64-linux-gnu/libc-2.30.so(textdomain+0x740) [0x3be70] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1d35) [0x35515] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 > /lib/x86_64-linux-gnu/libc-2.30.so(__open64_nocancel+0x4c) [0x11679c] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1ce9) [0x354c9] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] fstat(3, {st_mode=S_IFREG|0644, st_size=8994080, ...}) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__fxstat64+0x19) [0x1107b9] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1e33) [0x35613] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] mmap(NULL, 8994080, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc495795000 > /lib/x86_64-linux-gnu/libc-2.30.so(mmap64+0x26) [0x11baf6] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1e5d) [0x3563d] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] close(3) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x1eab) [0x3568b] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0xbdf) [0x343bf] > /lib/x86_64-linux-gnu/libc-2.30.so(setlocale+0x215) [0x339f5] > /bin/sleep() [0x25f0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(nanosleep+0x17) [0xe5d17] > /bin/sleep() [0x5827] > /bin/sleep() [0x5600] > /bin/sleep() [0x27b0] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xf3) [0x271e3] > /bin/sleep() [0x287e] close(1) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(_IO_file_close_it+0x70) [0x92fc0] > /lib/x86_64-linux-gnu/libc-2.30.so(fclose+0x166) [0x85006] > /bin/sleep() [0x5881] > /bin/sleep() [0x2d27] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x127) [0x49ba7] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep() [0x287e] close(2) = 0 > /lib/x86_64-linux-gnu/libc-2.30.so(__close_nocancel+0xb) [0x1165bb] > /lib/x86_64-linux-gnu/libc-2.30.so(_IO_file_close_it+0x70) [0x92fc0] > /lib/x86_64-linux-gnu/libc-2.30.so(fclose+0x166) [0x85006] > /bin/sleep() [0x5881] > /bin/sleep() [0x2d4d] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x127) [0x49ba7] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep() [0x287e] exit_group(0) = ? +++ exited with 0 +++ > /lib/x86_64-linux-gnu/libc-2.30.so(_exit+0x36) [0xe5fe6] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_secure_getenv+0x242) [0x49cc2] > /lib/x86_64-linux-gnu/libc-2.30.so(exit+0x20) [0x49d60] > /lib/x86_64-linux-gnu/libc-2.30.so(__libc_start_main+0xfa) [0x271ea] > /bin/sleep(+0x0) [0x287e]
صحيح ، لا يتم عرض أسماء الملفات المصدر وأرقام الأسطر هنا. addr2line
(إذا كانت هذه المعلومات موجودة من حيث المبدأ ، بالطبع).
هناك سؤال ثاني: بعض مكالمات النظام لا تحتوي على أغلفة في libc
. بعد ذلك يمكنك استخدام المجمع الشامل المسمى syscall
:
syscall(SYS_kcmp, getpid(), getpid(), KCMP_FILE, 1, fd)
الملف شيء غريب جدا ...
ليست مكالمات النظام مجرد وسيلة لطلب النواة الوصول إلى الجهاز نيابة عن العملية. إنها أيضًا واجهة برمجة تطبيقات عالمية مفهومة لجميع المكتبات في النظام. لذلك ، إذا كانت الوظيفة التي تحتاجها غير مدعومة في المكتبة ، فستعمل على الأرجح تلقائيًا إذا طلبت النواة بشكل صحيح. بالإضافة إلى ذلك ، يتم توريث جزء من "الإعدادات" للعملية بواسطة execve
، لذلك يمكنك محاولة القيام بذلك دون عكازات معقدة بمجرد تشكيل الحالة بشكل صحيح قبل بدء العملية (شيء مثل "لماذا نقل stderr
يدويًا إلى ملف ، إذا كان يمكنك فقط فتح الملف والقيام بذلك في FD # 2 للعملية الفرعية ").
مرة واحدة ، كنت بحاجة لطرح سلسلة من حزم الشبكة من ملف. في مرحلة ما ، تجاوز عدد العكازات جميع الحدود المعقولة ، وقررت أنه من غير المرجح أن يكون libpcap
أكثر تعقيدًا مما كتبت ، علاوة على ذلك ، كان هذا هو المعيار ، وكانت هناك أدوات مقبولة عمومًا لفتح هذه الملفات. اتضح أن استخدام libpcap
لقراءة المقالب يعد أمرًا صعبًا مثل fopen
لقراءة الملفات: يمكنك ببساطة فتح ملف التفريغ باستخدام pcap_(f)open_offline
الحزم من خلال pcap_next_ex
. هذا كل شئ! حسنًا ، ما زال الأمر يستحق إغلاق التفريغ عند الانتهاء من العمل ...
ولكن هنا تكمن المشكلة: يبدو أن libpcap
لا يمكنه القراءة من الذاكرة. ربما يستطيع ، بطبيعة الحال ، إذا بحثت فيه ، ولكن بالنسبة لـ "مختبرنا" ، سنتخيل أنه لا يستطيع ذلك.
على سبيل المثال ، مثال على ذلك: نحن ننتظر stdin
لسلسلة من البايتات ، وبعد ذلك يوجد تفريغ محاذي بواسطة 4 بايت. أدرك أنه يمكنك استخدام المدخلات المخزنة مؤقتًا وبعض ungetc
(نظرًا لأن libpcap
لا يزال يتطلب FILE *
) ، ولكن في الحالة العامة ، يمكننا فكها أثناء التنقل ، على سبيل المثال ، أو يمكن للمكتبة العمل مباشرة مع read
/ write
.
الحل 1: memfd_create
مكالمة النظام memfd_create
تسمح memfd_create
بإنشاء واصف ملف "مجهولة عمومًا". الملف موجود في الذاكرة وهو موجود بينما يكون هناك واصف واحد على الأقل مفتوحًا عليه. في أبسط الحالات ، يمكنك فقط الحصول على مثل هذا الواصف ، كتابة البيانات إليه من خلال write
، الترجيع lseek
، ومع lseek
دع libc
يعرف عنه:
int fd = memfd_create("pcap-dump-contents", 0); write(fd, buf, length); lseek(fd, 0, SEEK_SET); FILE *file = fdopen(fd, "r");
سيتم عرض اسم الملف الذي تم تمريره باستخدام الوسيطة الأولى في ارتباط في /proc/<PID>/fd
:
$ ls -l /proc/31747/fd 0 lr-x------ 1 trosinenko trosinenko 64 10 13:12 0 -> /path/to/128test.pcap lrwx------ 1 trosinenko trosinenko 64 10 13:12 1 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64 10 13:12 2 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64 10 13:12 23 -> '/home/trosinenko/.cache/appstream-cache-AH3OA0.mdb (deleted)' lrwx------ 1 trosinenko trosinenko 64 10 13:12 3 -> '/memfd:pcap-dump-contents (deleted)' lrwx------ 1 trosinenko trosinenko 64 10 13:12 57 -> 'socket:[41036]'
الحل 2: فتح باستخدام علامة O_TMPFILE
على نظام Linux ، بدءًا من إصدار ، عند إنشاء ملف ، يمكنك O_TMPFILE
واسم الدليل بدلاً من اسم الملف. نتيجةً لذلك ، يبدو أن الملف ، كحرف أدبي واحد يستخدم في القول (تقريبًا) ، موجود ، لكنه غير موجود ... لا أعرف إذا ما كانت البيانات مكتوبة على القرص ، لكن من المحتمل أن يعتمد على نظام الملفات (بالمناسبة ، يجب أن يدعم هذا الوضع) . لا يزال الملف يختفي عند إغلاق الرابط الأخير ، ولكن يمكن إرفاقه بشجرة الدليل باستخدام linkat
:
int fd = open(".", O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR); assert(fd != -1); assert(write(fd, buffer + offset, len - offset) == len - offset); assert(lseek(fd, 0, SEEK_SET) == 0); const char *link_to = getenv("LINK_TO"); if (link_to != NULL) { char path[128]; snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); linkat(AT_FDCWD, path, AT_FDCWD, link_to, AT_SYMLINK_FOLLOW); }
بالإضافة إلى فرصة عدم التعرض لتسمية الملف ، فإنه يجعل من الممكن ملء الملف ، وتكوين الحقوق ، وما إلى ذلك ، ثم الارتباط ذريًا بشجرة الدليل.
مثال (لكلا النهجين) #define _GNU_SOURCE #ifdef NDEBUG
$ fallocate -l 128 zero128 $ cat zero128 test.pcap > 128test.pcap $ ./memfd < 128test.pcap Found PCAP dump at offset 128 Read packet: full length = 105 bytes, available 105 bytes. Read packet: full length = 105 bytes, available 105 bytes. Read packet: full length = 66 bytes, available 66 bytes. Read packet: full length = 385 bytes, available 385 bytes. Read packet: full length = 66 bytes, available 66 bytes. ...
userfaultfd: معالجة أخطاء الذاكرة في userpace
أعتقد أنه لن يكون هناك شيء جديد جدًا في القول إنه في الأنظمة المشابهة لـ UNIX ، لا تشير واصفات الملفات إلى أي شيء. على سبيل المثال ، على Linux ، يمكن أن يكون مأخذ توصيل أو توجيه إخراج أو eventfd أو حتى رابطًا لبرنامج ebpf. ولكن ربما لا يزال هذا المثال مفاجأة لك. في بداية المقال تحدثت عن حقيقة أن أخطاء الصفحات هي أمر شائع بالنسبة للنواة: تبادل ، نسخ على ، هذا كل شيء ... عندما "تفوت" عملية المستخدم ، يتم إرسال SIGSEGV إليها. على حد علمي ، فإن إعادة التحكم من معالج SIGSEGV الذي تم إنشاؤه بواسطة kernel هو سلوك غير محدد ، ومع ذلك ، توجد مكتبة GNU libsigsegv التي تعمل على تعميم ميزات معالجة أخطاء الوصول إلى الذاكرة على العديد من الأنظمة الأساسية ، حتى Windows (ATTENTION: GPL license ، إن لم تكن جاهزة لـ توزيع البرنامج الخاص بك ، لا تستخدم libsigsegv) . منذ وقت ليس ببعيد ، ظهرت طريقة موثقة بالكامل في Linux ، تسمى userfaultfd
: باستخدام استدعاء النظام الذي يحمل نفس الاسم ، يمكنك فتح واصف للملفات ، والقراءة والكتابة التي تعد الهياكل الخاصة أوامر لها.
باستخدام واصف الملفات هذا ، يمكنك وضع علامة على مجموعة من العناوين الافتراضية لعملية الخاص بك. بعد ذلك ، عند أول وصول إلى كل صفحة من صفحات الذاكرة ، سيغفو التدفق ، وستعود القراءة من واصف الملف إلى معلومات حول ما حدث. بعد ذلك ، سوف يقوم المعالج بملء بنية الاستجابة بمؤشر للبيانات التي يجب استخدامها لتهيئة صفحة "المشكلة" ، وستقوم النواة بتهيئتها واستيقاظ الخيط الذي تحول. في هذه الحالة ، يُفترض وجود دفق منفصل ، تتضمن واجباته أوامر القراءة من الواصف وإصدار الإجابات. بشكل عام ، يمكن userfaultfd
الحصول على معلومات أخرى من خلال userfaultfd
، على سبيل المثال ، بعض الإعلامات حول تغيير البطاقة الافتراضية للعملية.
مثال للاستخدام #define _GNU_SOURCE #ifdef NDEBUG
$ ./userfaultfd UFFD open Before reading Fault: addr = 0x7f46f40d5000 Data at 0x7f46f40d5000: abababab
"السؤال الأساسي للرياضيات: هل هو كل نفس؟" ©
ماذا لو كنت بحاجة لمعرفة ما إذا كان واصف الملف هذا يشير إلى stdin
؟ يبدو أنه if (fd == 0) ...
- وهذا كل شيء. حسنا ، حسنا ...
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> int main() { int fd = dup(0); printf("stdin is fd %d, too\n", fd); if (fd == 0) printf("stdin"); else printf("not stdin"); return 0; }
$ gcc kcmp.c -o kcmp $ ./kcmp stdin is fd 3, too not stdin
عفوًا ... المقبض يشبه نوعًا ما ، لكن الأسماء المستعارة مختلفة. CRIU - نقطة تفتيش / استعادة في Userspace ستساعدنا . , , . userspace-, , , kcmp
: PID, , , , , :
#define _GNU_SOURCE #include <linux/kcmp.h> #include <syscall.h> #include <unistd.h> #include <stdio.h> int main() { int fd = dup(0); printf("stdin is fd %d, too\n", fd); int pid = getpid(); if (syscall(SYS_kcmp, pid, pid, KCMP_FILE /* _FILES! */, 0 /* stdin fd */, fd) == 0) printf("stdin\n"); else printf("not stdin\n"); if (syscall(SYS_kcmp, pid, pid, KCMP_FILE, 1 /* stdout fd */, fd) == 0) printf("stdout\n"); else printf("not stdout\n"); return 0; }
$ ./kcmp stdin is fd 3, too stdin stdout
! , ...
$ ls -l /proc/self/fd 0 lrwx------ 1 trosinenko trosinenko 64 10 14:45 0 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64 10 14:45 1 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64 10 14:45 2 -> /dev/pts/17 lrwx------ 1 trosinenko trosinenko 64 10 14:45 23 -> '/home/trosinenko/.cache/appstream-cache-AH3OA0.mdb (deleted)' lr-x------ 1 trosinenko trosinenko 64 10 14:45 3 -> /proc/17265/fd lrwx------ 1 trosinenko trosinenko 64 10 14:45 57 -> 'socket:[41036]'
, , , , . , bash , ls
!
$ ./kcmp < kcmp.c stdin is fd 3, too stdin not stdout
, — -, best effort - .
, ...
- … JIT- userspace? , eBPF- . , ( , , -) . ., JIT-, . , , BPF.
- … ? ,
sigaltstack
. - … , :
readahead
oldolduname
...