pahole(1) dwarves pahole(1)

pahole - نمایش و دستکاری چیدمان ساختار داده و چاپ خوانای داده‌های خام

pahole [options] files

دستور pahole چیدمان ساختارهای داده کدگذاری‌شده در قالب‌های اطلاعات دیباگ را نمایش می‌دهد؛ قالب‌های DWARF، CTF و BTF پشتیبانی می‌شوند.

این ابزار از جمله برای این موارد مفید است: بهینه‌سازی ساختارهای داده مهم از طریق کاهش اندازه آن‌ها، پی بردن به اینکه چه فیلدی در یک آفست مشخص از ابتدای ساختار داده قرار گرفته است، بررسی تغییرات ABI و به طور کلی درک یک پایگاه‌کد جدید که باید با آن کار کنید.

این ابزار همچنین از این چیدمان‌های ساختاری برای چاپ خوانای داده‌های ارائه‌شده به ورودی استاندارد خود استفاده می‌کند، به عنوان مثال:

$ pahole --header elf64_hdr --prettify /lib/modules/5.8.0-rc6+/build/vmlinux
{
	.e_ident = { 127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	.e_type = 2,
	.e_machine = 62,
	.e_version = 1,
	.e_entry = 16777216,
	.e_phoff = 64,
	.e_shoff = 604653784,
	.e_flags = 0,
	.e_ehsize = 64,
	.e_phentsize = 56,
	.e_phnum = 5,
	.e_shentsize = 64,
	.e_shnum = 80,
	.e_shstrndx = 79,
},
$

برای نمونه‌ها و مستندات بیشتر بخش «چاپ خوانا (PRETTY PRINTING)» را ببینید.

فایل‌ها باید اطلاعات دیباگ مرتبط را داشته باشند. این اطلاعات ممکن است در خود فایل، در بخش‌های ELF، یا در فایلی جداگانه باشد.

یک راه برای داشتن این اطلاعات، مشخص کردن گزینه -g برای کامپایلر در هنگام ساخت است. هنگامی که این کار انجام شود، اطلاعات در یک بخش ELF ذخیره خواهند شد. برای قالب اطلاعات دیباگ DWARF، این کار بخش ELF با نام .debug_info را (در میان بخش‌های دیگر) اضافه می‌کند. برای CTF، این اطلاعات فقط در یک بخش ELF با نام .SUNW_ctf یافت می‌شود. اطلاعات BTF دست‌کم در بخش ELF با نام .BTF و ممکن است در بخش .BTF.ext نیز ارائه شود.

بسته‌های debuginfo که در بیشتر توزیع‌های لینوکس در دسترس هستند نیز توسط pahole پشتیبانی می‌شوند، جایی که اطلاعات دیباگ در فایلی جداگانه در دسترس است.

به طور پیش‌فرض، pahole چیدمان تمام structهای نام‌گذاری‌شده در فایل‌های مشخص‌شده را نمایش می‌دهد.

اگر فایلی مشخص نشود، بررسی می‌کند که آیا مسیر /sys/kernel/btf/vmlinux موجود است یا خیر، و با استفاده از اطلاعات BTF موجود در آن درباره هسته در حال اجرا عمل می‌کند، یعنی این دستور کار می‌کند:

$ pahole list_head
struct list_head {
	struct list_head *         next;                 /*     0     8 */
	struct list_head *         prev;                 /*     8     8 */
	/* size: 16, cachelines: 1, members: 2 */
	/* last cacheline: 16 bytes */
};
$
امکان تغییر مسیر فایل /sys/kernel/btf/vmlinux با تنظیم متغیر محیطی PAHOLE_VMLINUX_BTF_FILENAME وجود دارد. این کار ممکن است برای آزمایش، اسکریپت‌نویسی در هنگام استفاده از یک BTF متفاوت برای vmlinux مفید باشد. همچنین در آزمون‌های پس‌رفت pahole استفاده می‌شود.

اگر BTF وجود نداشته باشد و هیچ فایلی ارسال نشود، آن‌گاه یک vmlinux مطابق با build-id هسته در حال اجرا در مکان‌های معمول، از جمله جایی که بسته‌های debuginfo هسته آن را قرار می‌دهند، جستجو خواهد شد تا به جای آن اطلاعات DWARF بررسی شود.

برای پیشنهادهای بیشتر در مورد نحوه استفاده، بخش «مثال‌ها (EXAMPLES)» را ببینید.

همچنین هر آنچه را که به ورودی استاندارد آن داده شود، بر اساس نوع مشخص‌شده، به صورت خوانا چاپ می‌کند؛ بخش مثال‌ها را ببینید.

از گزینه --count برای تعیین تعداد رکوردهایی که باید چاپ خوانا شوند استفاده کنید.

برنامه pahole از گزینه‌های زیر پشتیبانی می‌کند.

فقط نمایش همین کلاس‌ها. این مقدار می‌تواند یک لیست جداشده با کاما از نام‌های کلاس یا نشانی‌های URL فایل باشد (مانند: file://class_list.txt)
تنظیم اندازه cacheline بر حسب SIZE بایت.
مرتب‌سازی خروجی بر اساس نام نوع؛ شاید در آینده امکان مرتب‌سازی بر اساس معیارهای دیگر نیز اضافه شود.

این گزینه بیشتر به این دلیل مورد نیاز است که خروجی چاپ خوانا از BTF و DWARF در هنگام استفاده از چندین thread برای بارگذاری داده‌های DWARF قابل مقایسه باشد، زیرا در آن شرایط ترتیبی که انواع در واحدهای کامپایل (CUs) پردازش می‌شوند قطعی نیست.

تولید کد قابل کامپایل، با تمام تعاریف برای همه نوع‌ها، مانند:
$ pahole --compile > vmlinux.h

یک هدر تولید می‌کند که می‌تواند در یک فایل منبع C گنجانده شده و ساخته شود. در مثال ارائه‌شده، در صورت وجود از اطلاعات BTF استفاده می‌کند، در غیر این صورت به دنبال یک فایل DWARF مطابق با build-id هسته در حال اجرا می‌گردد.

در هنگام استفاده با گزینه‌هایی مانند --compile، عبارت 'typedef _Atomic int atomic_int' و موارد مشابه را درج نمی‌کند. اگر کامپایلر این‌ها را از قبل فراهم می‌کند از این گزینه استفاده کنید؛ از حدود سال ۲۰۲۲ با gcc 12.2.1 این موارد در DWARF کدگذاری نمی‌شوند، بنابراین برای تولید کد قابل کامپایل باید آن typedefها را برای انواع اتمیک استفاده‌شده در ساختارهای داده‌ای که از اطلاعات دیباگ استخراج می‌شوند صادر کنیم.
چاپ خوانای نخستین COUNT رکورد از ورودی.
نادیده گرفتن COUNT رکورد از ورودی.
گسترش اعضای کلاس. برای یافتن اینکه یک آفست از ابتدای یک struct در کدام عضو از structهای درونی قرار دارد مفید است.
امکان مشخص کردن فهرستی از قالب‌های دیباگ به ترتیب اولویت برای بررسی. در حال حاضر این شامل "btf"، "ctf" و "dwarf" است. مسیر قالب پیش‌فرض استفاده‌شده معادل "-F dwarf,btf,ctf" است.
امکان مشخص کردن تعداد بیت‌ها برای بارگذار قالب دیباگ. تاکنون تنها موردی که تحت تأثیر قرار گرفته "dwarf" است، پیش‌فرض کنونی آن ۱۵ و حداکثر آن در حال حاضر ۲۱ بیت است. می‌توانید آن را تغییر دهید تا ببینید آیا با تکامل هسته و نیاز به بارگذاری انواع و توابع بیشتر، کارایی بهبود می‌یابد یا خیر.
چاپ آفست‌ها و اندازه‌ها در مبنای شانزده (هگزادسیمال).
نمایش آفست‌های نسبی اعضا در structهای درونی.
گسترش اعضای اشاره‌گر کلاس.
سازمان‌دهی مجدد struct، تنزل رتبه و ترکیب bitfieldها، و جابجایی اعضا برای حذف حفره‌های تراز (alignment holes) و padding.
نمایش چیدمان struct در هر مرحله از سازمان‌دهی مجدد.
نمایش کلاس‌هایی که شامل CLASS_NAME هستند.
نمایش شمارشی (enumeration) که شامل ENUMERATOR_NAME است.
  $ pahole --contains_enumerator S_VERSION
  enum file_time_flags {
	  S_ATIME   = 1,
	  S_MTIME   = 2,
	  S_CTIME   = 4,
	  S_VERSION = 8,
  }
  $
شامل کردن کلاس‌های ناشناس (anonymous).
شامل کردن کلاس‌های ناشناس تو در تو (درون structهای دیگر).
تنها نمایش structهایی که دست‌کم NR_HOLES حفره بیتی دارند.
حالت بازگشتی، بر چندین فلگ دیگر نیز تأثیر می‌گذارد.
مستثنی کردن کلاس‌های اعلان‌شده در فایل‌های دارای پیشوند PREFIX.
یافتن اشاره‌گرها به CLASS_NAME.
تنها نمایش structهایی با دست‌کم NR_HOLES حفره.
تنها نمایش structهایی با SIZE_PADDING بایت padding در انتهای آن.
تنها نمایش structهایی با دست‌کم SIZE_PADDING بایت padding در انتهای آن.
نمایش فایل و شماره خطی که تگ‌ها در آن تعریف شده‌اند، در صورتی که در اطلاعات دیباگ موجود باشد.
به طور پیش‌فرض، متغیرهای VAR فقط برای متغیرهای percpu کدگذاری می‌شوند. در صورت مشخص شدن، این گزینه از کدگذاری هرگونه متغیر VAR جلوگیری می‌کند. توجه داشته باشید که این گزینه می‌تواند توسط ویژگی "global_var" لغو شود.
عدم کدگذاری تگ‌های اعلان (decl tags) در BTF.
عدم کدگذاری enum64 در BTF.
عدم کدگذاری تگ‌های نوع (type tags) در BTF.
عدم کدگذاری توابعی با چندین پیش‌نمونه (prototype) ناسازگار یا استفاده غیرمنتظره از رجیسترها برای پارامترهای آن‌ها، به طوری که رجیسترهای استفاده‌شده با قراردادهای فراخوانی همخوانی ندارند.
اجرای N وظیفه به صورت موازی. در صورت مشخص نشدن آرگومان، به صورت پیش‌فرض تعداد پردازنده‌های آنلاین به علاوه ۱۰٪ در نظر گرفته می‌شود (مشابه سیستم ساخت 'ninja').
کدگذاری اطلاعات BTF از روی DWARF، که در فرآیند ساخت هسته لینوکس هنگام فعال بودن CONFIG_DEBUG_INFO_BTF=y استفاده می‌شود و در لینوکس ۵.۲ معرفی شد. برای پیاده‌سازی ویژگی‌هایی مانند BPF CO-RE (یک بار کامپایل - همه‌جا اجرا) به کار می‌رود.

ببینید: https://nakryiko.com/posts/bpf-portability-and-co-re

همانند گزینه -J/--btf_encode، اما ذخیره اطلاعات خام BTF در یک فایل جداگانه.
نادیده گرفتن نمادهایی که هنگام کدگذاری BTF نامعتبر تشخیص داده می‌شوند.
مسیر فایل پایه BTF، به عنوان مثال vmlinux در هنگام کدگذاری اطلاعات BTF ماژول هسته. این مسیر ممکن است در هنگام درخواست برای /sys/kernel/btf/MODULE استنتاج شود و به صورت خودکار روی "/sys/kernel/btf/vmlinux" تنظیم گردد.
اجازه تولید مدخل‌های BTF_KIND_FLOAT در سیستم‌هایی که اطلاعات DWARF فایل vmlinux دارای انواع ممیز شناور (float) است.
تولید BTF برای توابع دارای پسوندهای مرتبط با بهینه‌سازی (مانند isra. و constprop.).
اجازه استفاده از تمام قابلیت‌های BTF پشتیبانی‌شده توسط pahole.
کدگذاری BTF با استفاده از لیست ویژگی‌های مشخص‌شده، یا تعیین 'default' برای همه ویژگی‌های استاندارد پشتیبانی‌شده. این گزینه می‌تواند به عنوان جایگزینی برای استفاده از چندین گزینه مرتبط با BTF به کار رود، و 'default' نشان‌دهنده مجموعه استانداردی از ویژگی‌های BTF است که برای تولید BTF هسته استفاده می‌شوند، بنابراین به عنوان یک میانبر برای آزمایش آخرین مجموعه ویژگی‌های استاندارد مفید است. با این حال ساخت‌های هسته به جای 'default' ویژگی‌های خاص را مشخص خواهند کرد تا اطمینان حاصل شود که ویژگی‌های مورد نظر صرف‌نظر از نسخه pahole و مجموعه پیش‌فرض مربوطه فعال هستند. ویژگی‌های استاندارد پشتیبانی‌شده عبارتند از:
encode_force       Ignore invalid symbols when encoding BTF; for example
                   if a symbol has an invalid name, it will be ignored
                   and BTF encoding will continue.
var                Encode percpu variables using BTF_KIND_VAR in BTF.
float              Encode floating-point types in BTF.
decl_tag           Encode declaration tags using BTF_KIND_DECL_TAG.
type_tag           Encode type tags using BTF_KIND_TYPE_TAG.
enum64             Encode enum64 values with BTF_KIND_ENUM64.
optimized_func     Encode representations of optimized functions
                   with suffixes like ".isra.0".
consistent_func    Avoid encoding inconsistent static functions.
                   These occur when a parameter is optimized out
                   in some CUs and not others, or when the same
                   function name has inconsistent BTF descriptions
                   in different CUs.

ویژگی‌های غیر استاندارد پشتیبانی‌شده (که در 'default' فعال نیستند):

attributes         Allow generation of decl_tags and type_tags with
                   kind_flag = 1. These tags can be used to encode
                   arbitrary compiler attributes in BTF, but are
                   backward incompatible.
decl_tag_kfuncs    Inject a BTF_KIND_DECL_TAG for each discovered kfunc.
distilled_base     For split BTF, generate a distilled version of
                   the associated base BTF to support later relocation
                   of split BTF with a possibly changed base, storing
                   it in a .BTF.base ELF section.
global_var         Encode all global variables using BTF_KIND_VAR in BTF.

بنابراین به عنوان مثال، مشخص کردن --btf_encode=var,enum64 منجر به کدگذاری BTF خواهد شد که (علاوه بر کدگذاری اطلاعات پایه BTF) شامل متغیرها و مقادیر enum64 نیز خواهد بود.

اگر بخواهید یک ویژگی اضافی به مجموعه ویژگی‌های استاندارد بیفزایید، می‌توان از پیشوند '+' استفاده کرد، یعنی: --btf_features=+reproducible_build تمام ویژگی‌های پیش‌فرض را به علاوه ویژگی اضافی 'reproducible_build' اضافه می‌کند.

مشابه --btf_features در بالا، اما در صورت برخورد با یک ویژگی ناشناخته، pahole خارج خواهد شد.
نمایش مجموعه ویژگی‌های BTF پشتیبانی‌شده توسط گزینه --btf_features و خروج. برای بررسی اینکه کدام ویژگی‌ها پشتیبانی می‌شوند مفید است، چرا که --btf_features در صورت تعیین ویژگی ناشناخته خطایی صادر نمی‌کند.
جستجو برای یک vmlinux مطابق با build-id هسته در حال اجرا (از /sys/kernel/notes)، که ممکن است از یک سرور debuginfo دریافت شود.
نمایش نخستین عضو نوع پایه‌ای (base_type) با بزرگ‌ترین اندازه.
نمایش تعداد متدهای تمام کلاس‌ها، یعنی تعداد توابعی که آرگومان‌هایی از نوع اشاره‌گر به یک کلاس مشخص دارند.

برای به دست آوردن تعداد متدها برای یک کلاس خاص، لطفاً استفاده کنید:

$ pahole --nr_methods | grep -w sock
sock  1005
$ 

در مثال بالا از اطلاعات BTF موجود در /sys/kernel/btf/vmlinux استفاده شده است.

تنها نمایش اعضایی که در چیدمان کلاس فضا اشغال می‌کنند. متدهای ++C نمایش داده نخواهند شد.
نمایش تعداد اعضا.
نمایش اندازه کلاس‌ها.
نمایش تگ با DWARF OFFSET مشخص.
تنها نمایش structهایی که دارای حفره‌هایی هستند که با سازمان‌دهی مجدد اعضا قابل بسته‌بندی و پر شدن هستند، به عنوان مثال هنگام استفاده از گزینه --reorganize.
تنها نمایش structهایی که دارای یک آرایه انعطاف‌پذیر (flexible array) هستند.
تنها نمایش structهایی که دارای یک آرایه انعطاف‌پذیر توکار هستند، یعنی struct درونی که دارای آرایه‌های انعطاف‌پذیر است، یک آرایه انعطاف‌پذیر «در وسط».
نمایش خروجی کوتاه‌تر و آرام‌تر.
نمایش اندازه کلاس‌ها.
استفاده از SEP به عنوان جداکننده فیلدها.
نمایش اینکه struct چند بار تعریف شده است.
نمایش واحدهای کامپایلی (CUs) که CLASS_NAME (-C) در آن‌ها تعریف شده است.
تخت کردن آرایه‌ها، به طوری که array[10][2] تبدیل به array[20] شود. برای آزمودن و مقایسه خروجی‌های هر دو کدگذاری CTF/BTF و DWARF برای یک باینری یکسان مفید است.
حذف نشانگرهای تراز اجباری، تا بتوان خروجی BTF یا CTF را (که فاقد این اطلاعات هستند) با خروجی DWARF نسخه ۵ یا بالاتر مقایسه کرد.
حذف padding اجباری bitfield در انتهای structها، زیرا این امر به قابلیتی مانند DW_AT_alignment در DWARF نیاز دارد، تا بتوان خروجی BTF یا CTF را که فاقد این اطلاعات هستند مقایسه نمود.
حذف خروجی استنتاج __attribute__((__packed__))، تا بتوان خروجی BTF یا CTF را مقایسه کرد؛ الگوریتم استنتاج از مواردی مانند DW_AT_alignment استفاده می‌کند، بنابراین تا زمان بهبود آن برای استنتاج این مورد در BTF نیز، امکان غیرفعال کردن این خروجی فراهم شده است.
تبدیل bitfieldهای نامناسب مانند "int foo:32" به شکل ساده "int foo".
نمایش توضیحات با جزئیات کامل.
چاپ آمارهای مربوط به ساختارهای داده ptr_table، که برای نگهداری تمام انواع، تگ‌ها و ساختارهای داده توابع استفاده می‌شوند، به منظور تنظیم بهینگی توسعه چنین جداولی برای یک فایل vmlinux متداول در سال ۲۰۲۱.
تغییر اندازه کلمه معماری به WORD_SIZE.
مستثنی کردن کلاس‌های دارای پیشوند PREFIX.
مستثنی کردن واحدهای کامپایل دارای پیشوند PREFIX.
فقط پردازش واحدهای کامپایلی که از کد منبع نوشته‌شده به زبان‌های مشخص ایجاد شده‌اند.

زبان‌های پشتیبانی‌شده:

ada83, ada95, asm, bliss, c, c89, c99, c11, c++, c++03, c++11, c++14, cobol74,
cobol85, d, dylan, fortran77, fortran90, fortran95, fortran03, fortran08,
go, haskell, java, julia, modula2, modula3, objc, objc++, ocaml, opencl,
pascal83, pli, python, renderscript, rust, swift, upc

برای نمونه، هسته لینوکس در حدود سال ۲۰۲۲ با 'c89' نوشته شده است، از آن در فیلترها استفاده کنید.

عدم پردازش واحدهای کامپایلی که از کد منبع نوشته‌شده به زبان‌های مشخص ایجاد شده‌اند.

به عنوان مثال، برای نادیده گرفتن واحدهای کامپایل نوشته‌شده به زبان Rust، استفاده کنید از:

pahole -j --btf_encode --lang_exclude rust

این کار همچنین با تنظیم متغیر محیطی PAHOLE_LANG_EXCLUDE با همان لیست زبان‌ها قابل انجام است؛ برای نمونه، دستور زیر معادل عبارت بالا است:

export PAHOLE_LANG_EXCLUDE=rust
pahole -j --btf_encode

به این معنا که در هر دو حالت، واحدهای کامپایل 'rust' نادیده گرفته خواهند شد.

این قابلیت برای اسکریپت‌ها مناسب است چرا که روشی برای درخواست این استثناء برای pahole و pfunct بدون نیاز به استفاده از lang_exclude-- در تمام فراخوانی‌ها فراهم می‌سازد، فقط کافیست آن متغیر محیطی تنظیم شود.

شامل کردن کلاس‌های دارای پیشوند PREFIX.
تنها نمایش structهایی با دست‌کم یک حفره بزرگ‌تر یا مساوی با HOLE_SIZE.
تنها نمایش structها؛ تمام فیلترهای دیگر نیز اعمال می‌شوند، یعنی برای نمایش فقط اندازه‌های همه structها می‌توانید --structs را با --sizes ترکیب کنید، و غیره.
تنها نمایش structهای فشرده (packed)؛ تمام فیلترهای دیگر نیز اعمال می‌شوند، یعنی برای نمایش فقط اندازه‌های تمام structهای packed می‌توانید --packed را با --sizes ترکیب کنید، و غیره.
تنها نمایش unionها؛ تمام فیلترهای دیگر اعمال می‌شوند، یعنی برای نمایش فقط اندازه‌های همه unionها می‌توانید --union را با --sizes ترکیب کنید، و غیره.
نمایش نسخه سنتی به صورت رشته‌ای، مانند: "v1.18".
نمایش نسخه به صورت صرفاً عددی، مناسب برای استفاده در Makefileها و اسکریپت‌هایی که می‌خواهند بدانند آیا نسخه نصب‌شده قابلیت خاصی دارد یا خیر، مانند: 118 به جای "v1.18".
هنگامی که پیشوند رشته STRING باشد، با رشته به عنوان STRING رفتار شود.

برای فعال‌سازی تولید اطلاعات دیباگ در فرآیند ساخت هسته لینوکس، CONFIG_DEBUG_INFO را انتخاب کنید. این کار می‌تواند با استفاده از make menuconfig از طریق این مسیر انجام شود: "Kernel Hacking" -> "Compile-time checks and compiler options" -> "Compile the kernel with debug info". همچنین توصیه می‌شود CONFIG_DEBUG_INFO_BTF را با طی کردن مسیر menuconfig ذکرشده و سپس انتخاب "Generate BTF typeinfo" فعال کنید. اکثر توزیع‌های مدرن با پشتیبانی eBPF باید این گزینه را در تمامی هسته‌های خود فعال داشته باشند، که استفاده از pahole را تا حد زیادی تسهیل می‌کند.

بسیاری از توزیع‌ها همچنین بسته‌های debuginfo را ارائه می‌دهند، بنابراین فقط کافی است آن را در پیکربندی مخزن مدیر بسته خود فعال کنید و kernel-debuginfo یا هر برنامه فضای کاربری دیگری که به زبانی نوشته شده که کامپایلر برای آن اطلاعات دیباگ تولید می‌کند (مانند C یا ++C) را نصب نمایید.

تمام مثال‌های این بخش یا از /sys/kernel/btf/vmlinux (در صورت وجود) استفاده می‌کنند، یا با استفاده از اطلاعات build-id موجود در /sys/kernel/notes به دنبال فایل vmlinux مطابق با هسته در حال اجرا می‌گردند تا از تطابق آن اطمینان حاصل شود.

نمایش یک نوع داده:

$ pahole -C __u64
typedef long long unsigned int __u64;
$

اگر تنها آرگومان یک نام نوع باشد نیز کار می‌کند:

$ pahole raw_spinlock_t
typedef struct raw_spinlock raw_spinlock_t;
$

می‌توان چندین نوع داده را با کاما جدا کرده و ارسال نمود:

$ pahole raw_spinlock_t,raw_spinlock
struct raw_spinlock {
	arch_spinlock_t            raw_lock;             /*     0     4 */
	/* size: 4, cachelines: 1, members: 1 */
	/* last cacheline: 4 bytes */
};
typedef struct raw_spinlock raw_spinlock_t;
$

انواع داده می‌توانند گسترش یابند:

$ pahole -E raw_spinlock
struct raw_spinlock {
        /* typedef arch_spinlock_t */ struct qspinlock {
                union {
                        /* typedef atomic_t */ struct {
                                int counter;                                                  /*     0     4 */
                        } val;                                                                /*     0     4 */
                        struct {
                                /* typedef u8 -> __u8 */ unsigned char locked;                /*     0     1 */
                                /* typedef u8 -> __u8 */ unsigned char pending;               /*     1     1 */
                        };                                                                    /*     0     2 */
                        struct {
                                /* typedef u16 -> __u16 */ short unsigned int locked_pending; /*     0     2 */
                                /* typedef u16 -> __u16 */ short unsigned int tail;           /*     2     2 */
                        };                                                                    /*     0     4 */
                };                                                                            /*     0     4 */
        } raw_lock;                                                                           /*     0     4 */
        /* size: 4, cachelines: 1, members: 1 */
        /* last cacheline: 4 bytes */
};
$

هنگام رمزگشایی اشکالات OOPS هسته، ممکن است بخواهید آفست‌ها و اندازه‌ها را در مبنای ۱۶ (هگزادسیمال) مشاهده کنید:

$ pahole --hex thread_struct
struct thread_struct {
        struct desc_struct         tls_array[3];         /*     0  0x18 */
        long unsigned int          sp;                   /*  0x18   0x8 */
        short unsigned int         es;                   /*  0x20   0x2 */
        short unsigned int         ds;                   /*  0x22   0x2 */
        short unsigned int         fsindex;              /*  0x24   0x2 */
        short unsigned int         gsindex;              /*  0x26   0x2 */
        long unsigned int          fsbase;               /*  0x28   0x8 */
        long unsigned int          gsbase;               /*  0x30   0x8 */
        struct perf_event *        ptrace_bps[4];        /*  0x38  0x20 */
        /* --- cacheline 1 boundary (64 bytes) was 24 bytes ago --- */
        long unsigned int          debugreg6;            /*  0x58   0x8 */
        long unsigned int          ptrace_dr7;           /*  0x60   0x8 */
        long unsigned int          cr2;                  /*  0x68   0x8 */
        long unsigned int          trap_nr;              /*  0x70   0x8 */
        long unsigned int          error_code;           /*  0x78   0x8 */
        /* --- cacheline 2 boundary (128 bytes) --- */
        struct io_bitmap *         io_bitmap;            /*  0x80   0x8 */
        long unsigned int          iopl_emul;            /*  0x88   0x8 */
        mm_segment_t               addr_limit;           /*  0x90   0x8 */
        unsigned int               sig_on_uaccess_err:1; /*  0x98: 0 0x4 */
        unsigned int               uaccess_err:1;        /*  0x98:0x1 0x4 */
        /* XXX 30 bits hole, try to pack */
        /* XXX 36 bytes hole, try to pack */
        /* --- cacheline 3 boundary (192 bytes) --- */
        struct fpu                 fpu;                  /*  0xc0 0x1040 */
        /* size: 4352, cachelines: 68, members: 20 */
        /* sum members: 4312, holes: 1, sum holes: 36 */
        /* sum bitfield members: 2 bits, bit holes: 1, sum bit holes: 30 bits */
};
$

بسیار خب، می‌دانیم آفستی که باعث خطا شده در یک 'struct thread_struct' قرار دارد و آفست 0x178 است، بنابراین باید در struct با نام 'fpu' باشد... مشکلی نیست، 'struct thread_struct' را گسترش داده و با grep ترکیب می‌کنیم:

$ pahole --hex -E thread_struct | egrep '(0x178|struct fpu)' -B4 -A4
        /* XXX 30 bits hole, try to pack */
        /* XXX 36 bytes hole, try to pack */
        /* --- cacheline 3 boundary (192 bytes) --- */
        struct fpu {
                unsigned int       last_cpu;                                             /*  0xc0   0x4 */
                /* XXX 4 bytes hole, try to pack */
--
                                /* typedef u8 -> __u8 */ unsigned char alimit;           /* 0x171   0x1 */
                                /* XXX 6 bytes hole, try to pack */
                                struct math_emu_info * info;                             /* 0x178   0x8 */
                                /* --- cacheline 6 boundary (384 bytes) --- */
                                /* typedef u32 -> __u32 */ unsigned int entry_eip;       /* 0x180   0x4 */
                        } soft; /* 0x100  0x88 */
                        struct xregs_state {
$

می‌خواهید بدانید 'struct thread_struct' در کجای کدهای منبع هسته تعریف شده است؟

$ pahole -I thread_struct | head -2
/* Used at: /sys/kernel/btf/vmlinux */
/* <0> (null):0 */
$

در BTF موجود نیست، بنابراین از DWARF استفاده می‌کنیم که کمی بیشتر طول می‌کشد و فرض می‌شود فایل vmlinux مطابق را پیدا کند:

$ pahole -Fdwarf -I thread_struct | head -2
/* Used at: /home/acme/git/linux/arch/x86/kernel/head64.c */
/* <3333> /home/acme/git/linux/arch/x86/include/asm/processor.h:485 */
$

برای یافتن بزرگ‌ترین ساختارهای داده در هسته لینوکس:

$ pahole -s | sort -k2 -nr | head -5
cmp_data               290904 1
dec_datas              274520 1
cpu_entry_area         217088 0
pglist_data            172928 4
saved_cmdlines_buffer  131104 1
$

ستون دوم اندازه بر حسب بایت و ستون سوم تعداد حفره‌های تراز در آن ساختار است.

نمایش ساختارهای داده‌ای که دارای raw spinlock هستند و به سازوکار RCU ارتباط دارند:

$ pahole --contains raw_spinlock_t --prefix rcu
rcu_node
rcu_data
rcu_state
$

برای دیدن آن در متن اصلی، آن را با grep ترکیب کنید:

$ pahole rcu_state | grep raw_spinlock_t -B1 -A5
	/* --- cacheline 52 boundary (3328 bytes) --- */
	raw_spinlock_t             ofl_lock;             /*  3328     4 */
	/* size: 3392, cachelines: 53, members: 35 */
	/* sum members: 3250, holes: 7, sum holes: 82 */
	/* padding: 60 */
};
$

ابزار pahole همچنین می‌تواند از انواع ساختارهای داده برای چاپ خوانای داده‌های خام مشخص‌شده از طریق --prettify استفاده کند. برای خواندن داده‌های خام از ورودی استاندارد، کافی است از '--prettify -' استفاده کنید.

همچنین می‌تواند داده‌های خام را از stdin با توجه به نوع مشخص‌شده به صورت خوانا چاپ کند:

$ pahole -C modversion_info drivers/scsi/sg.ko
struct modversion_info {
      long unsigned int          crc;                  /*     0     8 */
      char                       name[56];             /*     8    56 */
      /* size: 64, cachelines: 1, members: 2 */
};
$
$ objcopy -O binary --only-section=__versions drivers/scsi/sg.ko versions
$
$ ls -la versions
-rw-rw-r--. 1 acme acme 7616 Jun 25 11:33 versions
$
$ pahole --count 3 -C modversion_info drivers/scsi/sg.ko --prettify versions
{
      .crc = 0x8dabd84,
      .name = "module_layout",
},
{
      .crc = 0x45e4617b,
      .name = "no_llseek",
},
{
      .crc = 0xa23fae8c,
      .name = "param_ops_int",
},
$
$ pahole --skip 1 --count 2 -C modversion_info drivers/scsi/sg.ko --prettify - < versions
{
      .crc = 0x45e4617b,
      .name = "no_llseek",
},
{
      .crc = 0xa23fae8c,
      .name = "param_ops_int",
},
$
این معادل دستور زیر است:
$ pahole --seek_bytes 64 --count 1 -C modversion_info drivers/scsi/sg.ko --prettify versions
{
	.crc = 0x45e4617b,
	.name = "no_llseek",
},
$
چاپ خوانا مطابق با این کلاس. می‌توان آرگومان‌هایی را به آن ارسال کرد تا نحوه انجام چاپ خوانا را تحت تأثیر قرار دهند، برای نمونه:
-C 'perf_event_header(sizeof,type,type_enum=perf_event_type,filter=type==PERF_RECORD_EXIT)'

این دستور 'struct perf_event_header' را به عنوان نوع مورد استفاده برای چاپ خوانای رکوردها انتخاب می‌کند، مشخص می‌کند که فیلد 'size' در آن struct باید برای تعیین اندازه رکورد (رکوردهای با اندازه متغیر) استفاده شود، 'enum perf_event_type' باید برای چاپ خوانای مقدار عددی در perf_event_header->type به کار رود و علاوه بر این برای جستجوی ابتکاری structهایی با نام یکسان (با حروف کوچک) با مدخل enum که از فیلد type تبدیل شده است استفاده شود و به جای نوع پایه 'perf_event_header' برای چاپ خوانا به کار رود. بخش «مثال‌های چاپ خوانا (PRETTY PRINTING EXAMPLES)» را در زیر ببینید.

علاوه بر این، بخش 'filter=' را می‌توان استفاده کرد که تاکنون فقط با عملگر '==' برای فیلتر کردن بر اساس فیلد 'type' و تبدیل رشته 'PERF_RECORD_EXIT' به یک عدد مطابق با type_enum عمل می‌کند.

آرگومان 'sizeof' به طور پیش‌فرض به نام عضو 'size' تنظیم می‌شود؛ اگر نام متفاوت باشد، می‌توان از فرم 'sizeof=sz' استفاده کرد. همین موضوع برای فیلد 'type=other_member_name' نیز برقرار است که به طور پیش‌فرض 'type' در نظر گرفته می‌شود.

بررسی هدر ELF برای یک فایل vmlinux با استفاده از BTF؛ ابتدا نوع هدر ELF را پیدا می‌کنیم:

$ pahole --sizes | grep -i elf | grep -i _h
elf64_hdr	64	0
elf32_hdr	52	0
$ 

اکنون می‌توانیم از این برای نمایش نخستین رکورد از آفست صفر استفاده کنیم:

$ pahole -C elf64_hdr --count 1 --prettify /lib/modules/5.8.0-rc3+/build/vmlinux
{
	.e_ident = { 127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	.e_type = 2,
	.e_machine = 62,
	.e_version = 1,
	.e_entry = 16777216,
	.e_phoff = 64,
	.e_shoff = 775923840,
	.e_flags = 0,
	.e_ehsize = 64,
	.e_phentsize = 56,
	.e_phnum = 5,
	.e_shentsize = 64,
	.e_shnum = 80,
	.e_shstrndx = 79,
},
$

این معادل دستور زیر است:

$ pahole --header elf64_hdr --prettify /lib/modules/5.8.0-rc3+/build/vmlinux

گزینه --header همچنین امکان ارجاع در سایر گزینه‌های خط فرمان به فیلدهای موجود در هدر را فراهم می‌کند. این قابلیت زمانی مفید است که بخواهید چندین رکورد را در یک فایل نمایش دهید و بازه‌ای که آن فیلدها در آن قرار دارند در فیلدهای هدر مشخص شده باشد، مانند فایل‌های perf.data:

$ pahole --hex ~/bin/perf --header perf_file_header --prettify perf.data
{
	.magic = 0x32454c4946524550,
	.size = 0x68,
	.attr_size = 0x88,
	.attrs = {
		.offset = 0xa8,
		.size = 0x88,
	},
	.data = {
		.offset = 0x130,
		.size = 0x588,
	},
	.event_types = {
		.offset = 0,
		.size = 0,
	},
	.adds_features = { 0x16717ffc, 0, 0, 0 },
},
$

بنابراین برای نمایش رکوردهای cgroups در بخش perf_file_header.data می‌توانیم از دستور زیر استفاده کنیم:

$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,type_enum=perf_event_type,filter=type==PERF_RECORD_CGROUP)' --prettify perf.data
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 40,
	},
	.id = 1,
	.path = "/",
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 48,
	},
	.id = 1553,
	.path = "/system.slice",
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 48,
	},
	.id = 8,
	.path = "/machine.slice",
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 128,
	},
	.id = 7828,
	.path = "/machine.slice/libpod-42be8e8d4eb9d22405845005f0d04ea398548dccc934a150fbaa3c1f1f9492c2.scope",
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 88,
	},
	.id = 13,
	.path = "/machine.slice/machine-qemu\x2d1\x2drhel6.sandy.scope",
},
$ 

برای حالت متداول که هدر دارای عضوی با فیلدهای 'offset' و 'size' باشد، می‌توان از این شکل فشرده‌تر استفاده کرد:

$ pahole ~/bin/perf --header=perf_file_header --range=data -C 'perf_event_header(sizeof,type,type_enum=perf_event_type,filter=type==PERF_RECORD_CGROUP)' --prettify perf.data

این دستور از ~/bin/perf برای دریافت تعاریف نوع استفاده می‌کند، 'struct perf_file_header' را به عنوان هدر تعریف می‌کند، سپس به اندازه '$header.data.offset' بایت از ابتدای فایل به جلو می‌رود و به اندازه '$header.data.size' بایت از چنین رکوردهایی را در نظر می‌گیرد. عبارت فیلتر می‌تواند یک پیشوند مشترک را حذف کند؛ در این مورد می‌توان آن را به طور معادل هم به صورت 'filter=type==CGROUP' نوشت یا حتی بخش '=filter' را نیز حذف کرد و به صورت فشرده 'type==CGROUP' درآورد:

اگر نگاه کنیم به:

$ pahole ~/bin/perf -C perf_event_header
struct perf_event_header {
	__u32                      type;                 /*     0     4 */
	__u16                      misc;                 /*     4     2 */
	__u16                      size;                 /*     6     2 */
	/* size: 8, cachelines: 1, members: 3 */
	/* last cacheline: 8 bytes */
};
$ 

و همچنین:

$ pahole ~/bin/perf -C perf_event_type
enum perf_event_type {
	PERF_RECORD_MMAP = 1,
	PERF_RECORD_LOST = 2,
	PERF_RECORD_COMM = 3,
	PERF_RECORD_EXIT = 4,
	PERF_RECORD_THROTTLE = 5,
	PERF_RECORD_UNTHROTTLE = 6,
	PERF_RECORD_FORK = 7,
	PERF_RECORD_READ = 8,
	PERF_RECORD_SAMPLE = 9,
	PERF_RECORD_MMAP2 = 10,
	PERF_RECORD_AUX = 11,
	PERF_RECORD_ITRACE_START = 12,
	PERF_RECORD_LOST_SAMPLES = 13,
	PERF_RECORD_SWITCH = 14,
	PERF_RECORD_SWITCH_CPU_WIDE = 15,
	PERF_RECORD_NAMESPACES = 16,
	PERF_RECORD_KSYMBOL = 17,
	PERF_RECORD_BPF_EVENT = 18,
	PERF_RECORD_CGROUP = 19,
	PERF_RECORD_TEXT_POKE = 20,
	PERF_RECORD_MAX = 21,
};
$

و علاوه بر این:

$ pahole ~/bin/perf -C perf_record_cgroup
struct perf_record_cgroup {
	struct perf_event_header   header;               /*     0     8 */
	__u64                      id;                   /*     8     8 */
	char                       path[4096];           /*    16  4096 */
	/* size: 4112, cachelines: 65, members: 3 */
	/* last cacheline: 16 bytes */
};
$

سپس می‌توانیم ببینیم چگونه perf_event_header.type می‌تواند از یک __u32 به یک رشته (PERF_RECORD_CGROUP) تبدیل شود. اگر آن عبارت type_enum=perf_event_type را حذف کنیم، تبدیل 'struct perf_event_header' به ساختار توصیفی‌تر 'struct perf_record_cgroup' را از دست خواهیم داد و همچنین فیلد header.type نیز زیبانویسی نخواهد شد:

$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,filter=type==19)' --prettify perf.data
{
	.type = 19,
	.misc = 0,
	.size = 40,
},
{
	.type = 19,
	.misc = 0,
	.size = 48,
},
{
	.type = 19,
	.misc = 0,
	.size = 48,
},
{
	.type = 19,
	.misc = 0,
	.size = 128,
},
{
	.type = 19,
	.misc = 0,
	.size = 88,
},
$

برخی از رکوردها در 'type_enum=perf_event_type' پیدا نمی‌شوند، بنابراین برخی رکوردها به نوعی که محتوای آن‌ها را به طور کامل نشان دهد تبدیل نمی‌شوند. برای perf می‌دانیم که آن‌ها در شمارش دیگری به نام 'enum perf_user_event_type' قرار دارند؛ بنابراین برای این موارد می‌توانیم یک «enum مجازی» بسازیم، یعنی مجموع دو enum، و سپس تمام آن ورودی‌ها رمزگشایی و به درستی تطبیق داده شوند؛ ابتدا چند رکورد نخست فقط با 'enum perf_event_type':

$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,type_enum=perf_event_type)' --count 4 --prettify perf.data
{
	.type = 79,
	.misc = 0,
	.size = 32,
},
{
	.type = 73,
	.misc = 0,
	.size = 40,
},
{
	.type = 74,
	.misc = 0,
	.size = 32,
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 40,
	},
	.id = 1,
	.path = "/",
},
$

اکنون با هر دو شمارش، یعنی با 'type_enum=perf_event_type+perf_user_event_type':

$ pahole ~/bin/perf --header=perf_file_header --seek_bytes '$header.data.offset' --size_bytes='$header.data.size' -C 'perf_event_header(sizeof,type,type_enum=perf_event_type+perf_user_event_type)' --count 5 --prettify perf.data
{
	.header = {
		.type = PERF_RECORD_TIME_CONV,
		.misc = 0,
		.size = 32,
	},
	.time_shift = 31,
	.time_mult = 1016803377,
	.time_zero = 435759009518382,
},
{
	.header = {
		.type = PERF_RECORD_THREAD_MAP,
		.misc = 0,
		.size = 40,
	},
	.nr = 1,
	.entries = 0x50 0x7e 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
},
{
	.header = {
		.type = PERF_RECORD_CPU_MAP,
		.misc = 0,
		.size = 32,
	},
	.data = {
		.type = 1,
		.data = "",
	},
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 40,
	},
	.id = 1,
	.path = "/",
},
{
	.header = {
		.type = PERF_RECORD_CGROUP,
		.misc = 0,
		.size = 48,
	},
	.id = 1553,
	.path = "/system.slice",
},
$

امکان ارسال چندین نوع وجود دارد؛ تنها باید مطمئن شد که آن‌ها در فایل به ترتیب ظاهر می‌شوند، یعنی برای مثال perf.data، به dump ساختار perf_file_header در بالا نگاه کنید؛ می‌توان ساختارهای perf_file_attr را در محدوده attrs هدر، و سپس perf_event_header را در محدوده data با دستور زیر چاپ کرد:

pahole ~/bin/perf --header=perf_file_header          -C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' --prettify perf.data

eu-readelf(1), readelf(1), objdump(1).

https://www.kernel.org/doc/ols/2007/ols2007v2-pages-35-44.pdf.

برنامه pahole توسط Arnaldo Carvalho de Melo <acme@kernel.org> نوشته شده و نگهداری می‌شود.

با تشکر از Andrii Nakryiko و Martin KaFai Lau برای ارائه کدگذار BTF و بهبود پایگاه‌کد، همراه با اطمینان از اینکه کدگذار BTF مطابق نیاز برای کدگذاری بخش .BTF هسته لینوکس از اطلاعات DWARF تولیدشده توسط gcc کار می‌کند. به همین منظور Andrii یک ابزار حذف تکرار BTF (deduplicator) در libbpf نوشت که توسط pahole استفاده می‌شود.

همچنین با تشکر از Conectiva، Mandriva و Red Hat برای فراهم آوردن فرصت کار بر روی این ابزارها.

لطفاً گزارش‌های اشکال را به <dwarves@vger.kernel.org> ارسال کنید.

نیازی به عضویت در فهرست پستی نیست.

January 16, 2020 dwarves