
متشمم نقل USB مباشر على مستوى النظام في eBPF — يفك ترميز حركة مرور USB أثناء مرورها (control SETUP وSCSI وHID) من خطافَي URB شاملَين. لا حاجة إلى usbmon ولا إلى متشمم عتادي. محمول عبر CO-RE.

خلاصة مباشرة وملوّنة لحركة USB على مستوى النظام — مبنية على نقطتي الاختناق الشاملتين لـ URB اللتين تمر عبرهما جميع سائقات وحدات التحكم المضيفة، لذا تعمل على xHCI/EHCI/OHCI/dwc على حدٍّ سواء دون نقاط تتبع لكل وحدة تحكم ودون usbmon. محمولة بالكامل عبر CO-RE.
| fentry hook | ما يخبرنا به |
|---|---|
usb_submit_urb | تمت جدولة نقل (الجهاز، نقطة النهاية، النوع، الحمولة) |
usb_hcd_giveback_urb | اكتمل (الحالة، البايتات المنقولة، زمن الاستجابة، الحمولة) |
يربط lru_hash المُفهرس بمؤشر URB الحدثين معًا: يسجّل الإرسال (submit) وقت البدء، وتقرؤه عملية الإكمال لحساب زمن الاستجابة من الإرسال إلى الإكمال، ثم تحذفه. يحاكي هذا اقتران الطلب/الاستجابة في httpbody — SUBMIT هي «الطلب» (ما يرسله المضيف)، وCOMPLETE هي «الاستجابة» (ما يعيده الجهاز).
تُفكَّك حزمة SETUP ذات الثمانية بايتات في عمليات نقل التحكم إلى اسم الطلب القياسي (GET_DESCRIPTOR, SET_CONFIGURATION, …)؛ وتُعرض مراحل البيانات كنص عندما تبدو نصّية وكعرض سداسي عشري (hexdump) بخلاف ذلك.
المخرجات سطر واحد لكل حدث (مدمجة). عند أول ظهور لجهاز تُضاف له سطر دليل ▸ («ناقل-جهاز» bus-dev، vid:pid، المنتج، سرعة الارتباط)؛ وبعد ذلك يحمل كل صف وسم DEV القصير فحسب، فتبقى الأعمدة اليسرى متراصة وسهلة المسح تحت حركة المرور الكثيفة. يعرض كل صف الوقت والنوع (SUBMIT/CMPLT) ونوع النقل وepNdir وسهم الاتجاه (← من الجهاز إلى المضيف IN، → من المضيف إلى الجهاز OUT) وعدد البايتات والحالة وزمن الاستجابة وسائق النواة المالك، ثم · وأكثر التفاصيل إفادة (SETUP مفككة، أمر SCSI، أو معاينة حمولة قصيرة). مرِّر --hex للحصول على عرض سداسي عشري كامل متعدد الأسطر بدلاً من ذلك. تُلوَّن البايتات السداسية حسب صنف قيمتها (الخالية أزرق، ASCII القابلة للطباعة سماوي، المسافات البيضاء أخضر، بقية محرفات التحكم أرجواني، القيم العالية/غير ASCII صفراء) على طرفية TTY؛ أما الناتج المُمرَّر عبر pipe فبلا ألوان.
usbmon. تُفكَّك حزم SETUP والحمولات بينما تجرّب على الجهاز.READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL).--errors-only حالات التوقف (EPIPE)، وانتهاء المهلة، وbabble، وأخطاء CRC عبر جميع الأجهزة في آنٍ واحد.INT أو كتابات تحكم SET_REPORT لم تكن أنت من أطلقها.--json بصيغة NDJSON؛ مرِّر الناتج إلى jq أو إلى ملف لمقارنة الحمولات بين التشغيلات.curl -fsSL https://yeet.cx | sh
ثم شغّله مباشرة من GitHub — يجلب yeet المثال ويبنيه لك دون الحاجة إلى استنساخ:
yeet run github:yeet-src/usbsnoop
للبناء من نسخة محلية بدلاً من ذلك:
make
يُفرغ BTF النواة في vmlinux.h (من أجل struct urb وusb_device ووصف الجهاز)، ثم يترجم. يتطلب clang وbpftool ونواة تدعم BTF.
yeet run . # all devices, runs until Ctrl-C
yeet run . -- --secs 30 # stop after 30s (prints a summary)
yeet run . -- --vid 0x320f # one vendor
yeet run . -- --vendor-id 0x046d --product-id 0xc52b # one device by id
yeet run . -- --bus 3 --dev 4 # one device by bus address
yeet run . -- --type control,int # only these transfer types
yeet run . -- --no-data # metadata only, skip payload capture
yeet run . -- --max-data 64 # cap rendered payload at 64 bytes
yeet run . -- --errors-only # only failed completions (stalls, timeouts)
yeet run . -- --hex # full multi-line hexdump per transfer
yeet run . -- --json | jq . # NDJSON, one object per event
تتم جميع عمليات التصفية داخل النواة، لذا لا تصل حركة المرور المستبعَدة إلى مساحة المستخدم أبدًا.
ينتهي كل سطر حدث بسائق النواة المالك بين قوسين ([hid_irq_in]، [usb_api_blocking_completion]) — يُرمَّز urb->complete داخل النواة عبر bpf_snprintf("%ps")، فلا حاجة للبحث في /proc/kallsyms. تفكّك عمليات النقل الشامل من نوع Bulk غلاف Bulk-Only Transport إلى أمر SCSI (CBW READ(10) lba=… blocks=… / CSW PASS). عند الخروج الموقوت (بلوغ --secs) يُطبع ملخص لكل جهاز ومدرج تكراري لزمن الاستجابة بمقياس log2؛ أما الخروج عبر Ctrl-C فيتخطى ذلك (لا يتوفر خطاف إشارة مرئي في JS).
غالبًا ما تُسلَّم النواة في حركة مرور Bulk (التخزين الشامل وأمثاله) مصفوفة struct scatterlist (urb->sg) بدلاً من transfer_buffer خطي واحد، لذا تنتشر الحمولة عبر الصفحات. يمشي usbsnoop عبر تلك المصفوفة وينسخ بايتات كل مقطع، لكن الوصول إليها يتطلب ترجمة الصفحة إلى عنوانها الافتراضي في النواة — وهو عكس page_to_virt في x86-64، الذي يحتاج page_offset_base وvmemmap_base للنواة الجارية (وكلاهما عشوائي عبر KASLR).
لا يمكن لبيئة JS المعزولة قراءة /proc/kallsyms ولا يدعم المحمِّل ksym، لذا تمرّر عنوانَي الرمزين وتقوم جهة BPF بفك الإشارة إليهما:
yeet run . -- \
--page-offset-base 0x$(sudo awk '$3=="page_offset_base"{print $1}' /proc/kallsyms) \
--vmemmap-base 0x$(sudo awk '$3=="vmemmap_base"{print $1}' /proc/kallsyms)
بدون تلك الخيارات، تظل عمليات نقل SG تعرض البيانات الوصفية كاملة، لكن دون بايتات الحمولة — وهو السلوك السابق. هذا المسار خاص بـ x86-64 فقط: على البنى الأخرى اترك الخيارات معطّلة.
actual/requested. يحمل كل سجل حلقي data[16384] كاملة، لذا تتسع الحلقة البالغة 8 MiB لنحو 512 حدثًا.--page-offset-base / --vmemmap-base أعلاه ومضيف x86-64؛ يُلتقط كل مقطع حتى حجم صفحة، ويُمرّ على أول 64 مقطعًا فقط من أي عملية نقل.| flag | default | meaning |
|---|
--secs | forever | مدة التشغيل؛ احذفه للتشغيل حتى Ctrl-C (رقم يوقف التشغيل ويطبع ملخصًا) |
--vid, --vendor-id | any | التصفية حسب معرّف المورّد (سداسي عشري 0x1d6b أو عشري) |
--pid, --product-id | any | التصفية حسب معرّف المنتج |
--bus | any | التصفية حسب رقم الناقل |
--dev | any | التصفية حسب عنوان الجهاز |
--type | all | قائمة مفصولة بفواصل من iso, int, control, bulk |
--no-data | off | لا يقرأ مخازن النقل المؤقتة (بيانات وصفية فقط) |
--max-data | 4096 | الحد الأقصى لبايتات الحمولة المعروضة لكل حدث |
--errors-only | off | يعرض حالات الإكمال غير الناجحة فقط (يتخطى SUBMIT وOK) |
--hex | off | عرض سداسي عشري كامل متعدد الأسطر لكل نقل (معاينة مدمجة سطرية بخلاف ذلك) |
--json | off | إخراج NDJSON (كائن واحد لكل حدث) بدلاً من عرض TTY |
--page-offset-base | off | عنوان النواة page_offset_base (سداسي عشري) — يفعّل التقاط حمولة SG (x86-64) |
--vmemmap-base | off | عنوان النواة vmemmap_base (سداسي عشري) — يُستعمل مع --page-offset-base |