Сделаем код чище: Специальные расширения vsnprintf() в ядре Linux

  • Tutorial
Смотря на кучу исходного кода, который засылают программисты в списки рассылки подсистем ядра Linux иногда хочется плакать. С одной стороны бывает ужасный и непотребный код, с другой — люди, возможно, впервые пытаются что-то сделать для ядра, поэтому не знают всех его особенностей.

Книга Linux Device Drivers устарела, а новая версия выйдет нескоро. Поэтому мне хочется заполнить пробелы в знаниях тех программистов, которые пишут код в ядро.

В данном материале я расскажу о специальных расширениях функции печати в ядре Linux. Не смущайтесь, что я в заголовок вынес традиционное её имя в основной библиотеке Си, в ядре чаще используется printk() и макросы вокруг неё.

Итак, за основу я возьму ванильное ядро версии 4.0-rc2. Рассматривать стандартные спецификаторы я не буду, любой желающий может прочитать printf(3).

Все специальные расширения приходятся на спецификатор %p. По умолчанию он печатает адрес памяти, на которую ссылается указатель. В ядре же зачастую хочется сделать что-то гораздо большее и специфичное. Для этого решили пойти путём добавления модификаторов к спецификатору, таким образом в общем случае спецификатор выглядит следующим образом:
%[длина поля]p[модификаторы в виде буквы, цифры или их сочетаний]


Ниже я попытался разбить все расширения на группы и дать краткие описания и иногда примеры.

Указатели на специальные адреса



%pK

То же, что и %p, но проверяется kptr_restrict sysctl, печатаются 0'и, если у пользователя недостаточно прав.

%pa[pd]

Печает указатель на адрес физической памяти или DMA (phys_addr_t, dma_addr_t) и наследованные типа resource_size_t. Передаётся по ссылке, например:

phys_addr_t pa;
dma_addr_t da;
pr_debug("Phys: %pa DMA: %pad\n", &pa, &da);


Сетевые адреса



%p[Mm][FR]

Печатает MAC адрес, передаваемый по указателю на буфер. M — стандартный MAC адрес, m ­— без двоеточий, дополнительный модификатор R в реверсном формате (вначале печатается последний байт адреса).

u8 mac[ETH_ALEN];
pr_debug("%pMR\n", mac);


%p[Ii]4[hnbl], %p[Ii]6[c], %p[Ii]S[pfschnbl]

Печатает в различных комбинациях адреса IPv4 (__be32 addr), IPv6 (__be32 addr[4]) и struct sockaddr (автоопределение).

Дамп буфера данных



%*pE[achnops]

Печатает строку с экранированными символами. Флагами определяются классы символов, которые необходимо экранировать.

const char *buf;
int len;
pr_debug("Buffer: %*pE Buffer[0-5]: %6pE\n", len, buf, buf);


%*ph[CDN]

Печатает дамп памяти (до 64 байт) в шестнадцатиричном виде. Модификаторами определяется разделитель: пробел по умолчанию, C — двоеточие, D — дефисы, N — без разделителя.

u8 data[100];
pr_debug("Buf: %*phC\n", (int)sizeof(data), data); /* only first 64 bytes! */


%pU[Ll][Bb]

Предназначена для вывода UUID (буфер длиной в 16 байт) в различных форматах. Модификаторы определяют размер букв (большие или маленькие) и порядок записи UUID: B или b — старшие байты вначале, L или l — младшие вначале.

%*pb[l]

Выводит дамп битовых массивов и его наследников (cpumask, nodemask). Модификатор l определяет вывод дампа диапазонами. Поле длины определяет сколько бит в одном элементе битового массива.

Содержимое структур, их полей и специальных типов данных



%p[Rr]

Печатает содержимое struct resource.

struct resource res;
pr_debug("%pR\n", &res);


%pV

Вывод данных, определяемых структурами va_format и va_list. По сути рекурсивный вызов vsnprintf() из-под себя самой. Обязательно проверяйте валидность параметров и аргументов в va_format и va_list!

%pNF

Спецификатор для типа netdev_features_t.

%pd[234], %pD[234]

Служит для печати пути и имени файла (struct dentry, поле d_name.name). 2,3 или 4 ограничивает количество элементов пути (с конца). %pD то же самое, но для struct file (f_path.dentry).

Печать имени функции по адресу



%p[Ff], %p[Ss][R], %pB

Спецификатор может быть полезен для печати имени функции по адресу, например, узнать вызвавшую функцию.

В будущем релизе



В 4.1-rc1 планируется включить дополнительные расширения.

%pC[nr]

Предназначен для вывода имени и частосты осциллятора, хранящихся в struct clk.

%pT

Выведет название текущего исполняемого процесса или задачи, определённой в struct task_struct.

struct task_struct *task = known_task;
pr_debug("Current: %pT, given: %pT\n", NULL, task);


Бонусы



Для вывода больших дампов буфера в шестнадцатиричном формате предназначена функция print_hex_dump().

Для преобразования данных ASCII <—> binary служит набор таких функций:

/* В бинарный вид */
hex_to_bin(); /* полубайт */
hex2bin(); /* буфер */


Чтобы преобразовать из текстового вида MAC адрес, воспользуйтесь mac_pton().

/* В ASCII формат */
hex_asc_lo(); hex_asc_hi(); /* полубайт */
hex_asc_upper_lo(); hex_asc_upper_hi(); /* то же, но большими буквами */
hex_byte_pack(); hex_byte_pack_upper(); /* байт */
bin2hex(); /* буфер */
hex_dump_to_buffer(); /* рабочее тело упоминаемой выше print_hex_dump() */


Удачной печати!
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 8

    +1
    Узнал много нового — спасибо! Для меня ядро — это что-то непонятное и даже страшное!.. А уж добавить или исправить в нём что-то — это вообще необъяснимое и недостижимое!.. Может быть и я когда-нибудь смогу сделать патч для ядра, но пока в это слабо верится.
      +5
      Я хочу в будущем написать статью, рассказывающую что новичок может улучшить или исправить в ядре. Забегая вперёд, могу подсказать следующую задачу (подразумевается владение английским хоть какое, понимание как работать c Git, азы языка Си и самое главное — рвение сделать это): в ядре ещё не до конца вычистили куски, где можно использовать %*ph. Такие места ищутся просто по паттернам %02x[ :-]02x[ :-]02x. Если есть интерес и желание, могу помочь более детально. Мой JID: andriy@jabber.ru.
        +3
        %02x[ :-]%02x[ :-]%02x конечно же!
        0
        Вот, написал: habrahabr.ru/post/253123/. Попробуйте обязательно, у вас всё получится!
        +2
        Хм. Эти суффиксные модификаторы, во-первых, ломают обратную совместимость. А во-вторых, приводят к неочевидному парсированию — нужно помнить все суффиксы, чтобы отличать их от просто букв, которые должны напечататься как есть.

        Можно же было префиксами выразиться, пусть даже ценой увеличения форматной строки на один символ.
          0
          Да, всё так, но ядро само по себе такая штука, где нужно помнить довольно большое число фишек.
            0
            … поэтому давайте добавим ещё одну.
            0
            Мы — в MySQL в свое время — как раз стали расширять префиксами и новыми модификаторами. Суффиксы — они не в стиле printf, это что-то новое. У нас все как раз соответствовало духу printf. был модификатор `, использовался как "%`s", был новый символ формата b, использовался как "%b". Минусы такого решения — в редакторе подсветка синтаксиса их не подсвечивает (мне-то пофиг, у меня vim, я его и подкрутить могу, другим мешает). И особенно неприятно — компилятор все время предупреждения выдает на неправильную форматную строку. А суффиксное решение компилятор не отвлекает.

          Only users with full accounts can post comments. Log in, please.