Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Руководство для начинающих пользователей SystemTap. Принцип работы SystemTap

Оригинал: SystemTap Beginners Guide
Авторы: Don Domingo, William Cohen
Дата публикации: 20 июля 2009 г.
Перевод: А.Панин
Дата перевода: 30 сентября 2014 г.

Глава 3. Принцип работы SystemTap

3.3. Базовые конструкции обработчиков SystemTap

SystemTap позволяет использовать некоторые базовые конструкции в рамках обработчиков. Синтаксис большинства этих конструкций, применяемых в рамках обработчиков, по большей части базируется на синтаксисе языков C и awk. В данном разделе приводятся описания нескольких наиболее полезных конструкций, применяемых в рамках обработчиков SystemTap, причем этой информации вполне достаточно для разработки простых, но мощных сценариев SystemTap.

3.3.1. Переменные

Переменные могут свободно использоваться в рамках обработчика; просто выберите имя переменной, присвойте ей значение, полученное от функции или выражения, после чего вы сможете использовать эту переменную в рамках другого выражения. SystemTap автоматически определяет, должна ли переменная иметь строковый и целочисленный тип, опираясь на информацию о типах присваиваемых значений. Например, если вы объявляете переменную foo и присваиваете ей значение, возвращаемое функцией gettimeofday_s() (следующим образом: foo = gettimeofday_s()), переменная foo будет иметь целочисленный тип, причем ее значение может быть выведено с помощью функции printf() при условии использования спецификатора целочисленного формата (%d).

Учтите, однако, что по умолчанию переменные являются локальными по отношению к зонду в рамках которого они используются. Это значит, что переменные инициализируются, используются и уничтожаются каждый раз при вызове обработчика зонда. Для того, чтобы разделить переменную между зондами, следует использовать идентификатор области действия global перед объявлением имени переменной вне обработчиков зондов. Рассмотрите следующий пример:

Пример 3.8. timer-jiffies.stp
global count_jiffies, count_ms
probe timer.jiffies(100) { count_jiffies ++ }
probe timer.ms(100) { count_ms ++ }
probe timer.ms(12345)
{
    hz=(1000*count_jiffies) / count_ms
    printf ("jiffies:ms ratio %d:%d => CONFIG_HZ=%d\n",
        count_jiffies, count_ms, hz)
    exit ()
}

В Примере 3.8, "timer_jiffies.stp" рассчитывается значение параметра CONFIG_HZ ядра ОС на основе данных, полученных с помощью таймеров, отсчитывающих количество тиков и миллисекунд. Идентификатор области действия global позволяет сценарию использовать переменные count_jiffies и count_ms (значения которых устанавливаются в рамках обработчиков соответствующих зондов), в рамках зонда probe timer.ms(12345).

Примечание
Нотация ++ из Примера 3.8, "timer-jiffies.stp" (используемая совместно с переменными count_jiffies ++ и count_ms ++) применяется для увеличения значения переменной на 1. В рамках обработчика следующего зонда значение переменной count_jiffies увеличивается на 1 через каждые 100 тиков:
probe timer.jiffies(100) { count_jiffies ++ }
			

В данном случае SystemTap делает вывод о том, что переменная count_jiffies является целочисленной. Так как начальное значение переменной count_jiffies не было установлено, в качестве ее начального значения было автоматически использовано нулевое значение.

3.2.2. Целевые переменные

В рамках зондов для событий, связанных с определенными фрагментами кода (например, kernel.function("функция")) или kernel.statement("объявление")) имеется возможность использования целевых переменных (target variables) для получения значений переменных, видимых из исследуемого фрагмента кода. Вы можете использовать аргумент -L для вывода списка всех доступных в точке исследования целевых переменных. В том случае, если для исследуемого ядра ОС установлен пакет с отладочной информацией, вы можете выполнить следующую команду для определения целевых переменных, доступных в рамках функции vfs_read:
stap -L 'kernel.function("vfs_read")'
После исполнения данной команды будет выведена информация, аналогичная следующей:
kernel.function("vfs_read@fs/read_write.c:227") $file:struct file*
$buf: char* $count:size_t $pos:loff_t*

Перед именем каждой целевой переменной добавляется символ "$", а после него - символ ":" с указанием на тип целевой переменной. Функция ядра ОС vfs_read содержит целевые переменные $file (указатель на структуру, описывающую файл), $buf (указатель на фрагмент памяти в пространстве пользователя для сохранения прочитанных данных), $count (целочисленное значение, соответствующее количеству байт для чтения) и $pos (целочисленное значение, соответствующее позиции в файле, с которой необходимо начинать чтение), доступные при входе в функцию.

В том случае, если целевая переменная не является локальной переменной для исследуемого фрагмента кода, а является такой переменной, как глобальная внешняя переменная или локальная статическая переменная, описанная в другом файле исходного кода, на нее можно сослаться следующим образом: "@var("varname@src/file.c")".

SystemTap отслеживает информацию о типизации целевых переменных и позволяет исследовать поля структур данных благодаря наличию оператора ->. Оператор -> может использоваться многократно для исследования полей структур данных, расположенных в рамках других структур данных, а также для следования по указателям на другие структуры данных. Также оператор -> позволяет получить значение поля структуры данных. Оператор -> используется вне зависимости от того, осуществляется ли доступ посредством указателя к полю вложенной структуры данных или к другой структуре данных.

Например, для доступа к полю статической целевой переменной files_stat, описанной в рамках файла fs/file_table.c (которая содержит некоторые текущие параметры файловой системы, заданные с помощью интерфейса sysctl), может быть использована следующая команда:
stap -e 'probe kernel.function("vfs_read") {
        printf ("current files_stat max-files: %d\n",
                @var("files_stat@fs/file_table.c")->max_files);
        exit(); }'
В результате исполнения которой будет выведена информация, аналогичная следующей:
current files_stat max_files: 386070
Для получения указателей на переменные базовых типов, такие, как целочисленные и строковые переменные, существуют функции доступа к данным пространства ядра ОС, приведенные в списке ниже. Первым аргументом каждой из функций является указатель на данные. Аналогичные функции для доступа к целевым переменным в коде из пространства пользователя описываются в Разделе 4.2. "Доступ к целевым переменным в пространстве пользователя".
kernel_char(адрес)
Функция для получения символа (char), расположенного в памяти ядра ОС по заданному адресу.
kernel_short(адрес)
Функция для получения короткого целочисленного значения (short int), расположенного в памяти ядра ОС по заданному адресу.
kernel_int(адрес)
Функция для получения целочисленного значения (int), расположенного в памяти ядра ОС по заданному адресу.
kernel_long(адрес)
Функция для получения длинного целочисленного значения (long int), расположенного в памяти ядра ОС по заданному адресу.
kernel_string(адрес)
Функция для получения строки, расположенной в памяти ядра ОС по заданному адресу.
kernel_string_n(адрес, n)
Функция для получения строки, расположенной в памяти ядра ОС по заданному адресу, с ограничением длины строки n байтами.

3.3.2.1. Отформатированный вывод значений целевых переменных

Сценарии SystemTap обычно используются для наблюдения за изменениями состояния системы, происходящими на уровне кода. В большинстве случаев для этого достаточно простого вывода значений переменных из различных контекстов. SystemTap предоставляет возможность использования множества операторов, которые позволяют генерировать отформатированные строки со значениями целевых переменных:
$$vars
Создает отформатированную строку, эквивалентную строке, которая создается с помощью функции sprintf("parm1=%x ... parmN=%x var1=%x ... varN=%x", parm1, ..., parmN, var1, ..., varN) с информацией о каждой переменной, видимой из фрагмента исследуемого кода. Вместо некоторых значений могут использоваться строки "=?" в том случае, если их адреса не могут быть определены в процессе функционирования системы.
$$locals
Создает отформатированную строку, являющуюся подстрокой строки, получаемой при использовании оператора $$vars, которая содержит исключительно значения локальных переменных.
$$params
Создает отформатированную строку, являющуюся подстрокой строки, получаемой при использовании оператора $$vars, которая содержит исключительно значения параметров функции.
$$return
Доступен только в зондах, предназначенных для отслеживания выходов из функций. Создает строку, эквивалентную строке, создаваемой при использовании функции sprintf("return=%x", $return) в том случае, если исследуемая функция возвращает значение или пустую строку в противном случае.
Ниже приведена команда для запуска сценария, который выводит значения параметров, переданных в функцию vfs_read:
stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms); exit(); }'
Функция vfs_read принимает четыре параметра: file, buf, count и pos. Оператор $$parms генерирует строку со значениями всех параметров, передаваемых функции. В данном случае все параметры за исключением параметра count представлены указателями. Ниже приведен пример вывода упомянутого ранее сценария, передаваемого с помощью командной строки:
file=0xffff8800b40d4c80 buf=0x7fff634403e0 count=0x2004 pos=0xffff8800af96df48
Адрес, на который указывает указатель, может оказаться бесполезным. В то же время, получение значения поля структуры данных, на которое указывает указатель, может иметь смысл. Следует использовать суффикс $ для отформатированного вывода значений полей структуры данных. В следующем примере сценария, передаваемого посредством командной строки, используется суффикс форматирования для вывода информации о структурах данных, переданных в функцию vfs_read:
stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$); exit(); }'

В ходе исполнения приведенной выше команды будет сгенерирован аналогичный вывод с информацией об именах и значениях полей структуры данных:

При использовании суффикса "$", поля, являющиеся структурами данных, не раскрываются. Суффикс "$$" позволяет вывести информацию о значениях полей вложенных структур данных. Ниже приведен пример использования суффикса "$$":
stap -e 'probe kernel.function("vfs_read") {printf("%s\n", $$parms$$); exit(); }'

Длина строки, генерируемой в случае использования суффикса "$$", как и длины всех других строк, ограничена максимальным значением длины строки. Ниже приведен пример вывода упомянутого ранее сценария, переданного с помощью командной строки, который усечен из-за ограничения длины строки:

3.3.2.2. Приведение типов

В большинстве случаев SystemTap может установить тип переменной, воспользовавшись отладочной информацией. Однако, в коде могут использоваться указатели void для передачи адресов переменных (например, в функциях резервирования памяти) и информация о типах переменных может быть недоступна. Также информация о типах переменных, доступная в рамках обработчика зонда, не является доступной в рамках функции; при передаче аргументов функций SystemTap используются длинные целочисленные значения вместо типизированных указателей. В таких случаях оператор @cast SystemTap (доступный начиная с версии SystemTap 0.9) может использоваться для указания корректного типа объекта.

Пример 3.9, "Пример преобразования типов" взят из тапсета task.stp. Приведенная функция возвращает значение поля state структуры task_struct, указатель на которую представлен в форме длинного целочисленного значения task. Первым аргументом оператора @cast является целочисленное значение task, представляющее указатель на объект. Вторым аргументом является строка с именем типа, к которому следует преобразовать объект, а именно, task_struct. Третьим необязательным аргументом является строка с именем файла исходного кода, из которого можно получить информацию об объявленном типе данных. В случае использования оператора @cast может осуществляться доступ к различным полям данной структуры данных task типа task_struct; в данном примере извлекается значение поля state.

Пример 3.9. Пример преобразования типов
function task_state:long (task:long)
{
    return @cast(task, "task_struct", "kernel<linux/sched.h>")->state
}

3.3.2.3. Проверка доступности целевой переменной

По мере доработки кода набор доступных целевых переменных может изменяться. Оператор @defined упрощает обработку подобных случаев изменения набора доступных целевых переменных. Оператор @defined позволяет провести тестирование, направленное на установление того, доступна ли определенная целевая переменная. Результат данного тестирования может быть использован для выбора подходящего оператора.

В Примере 3.10 "Пример тестирования доступности целевой переменной" из тапсета memory.stp реализован псевдоним события зонда. В некоторых версиях ядер ОС исследуемая функция имеет аргумент $flags. В том случае, если есть возможность, аргумент $flags используется для генерации локальной переменной write_access. Версии исследуемых функций, в которых не используется аргумент $flags, имеют аргумент $write, который используется вместо локальной переменной write_access.

Пример 3.10. Пример тестирования доступности целевой переменной
probe vm.pagefault = kernel.function("__handle_mm_fault@mm/memory.c") ?,
                     kernel.function("handle_mm_fault@mm/memory.c") ?
{
        name = "pagefault"
        write_access = (@defined($flags)
                        ? $flags & FAULT_FLAG_WRITE : $write_access)
        address =  $address
}

3.3.3. Операторы условных переходов

В некоторых случаях объем выводимой сценариями SystemTap информации может оказаться чрезмерно большим. Для решения данной проблемы вам придется дополнительно усовершенствовать логику сценария с целью выделения каких-либо фрагментов выводимой информации, которые являются наиболее актуальными или полезными для вашего исследования.

Вы можете сделать это с помощью операторов условных переходов (conditionals), которые должны использоваться в рамках обработчиков. SystemTap позволяет использовать следующие типы операторов условных переходов:

Операторы If/Else
Формат:
if (условие)
  оператор1
else
  оператор2
  			

Оператор оператор1 выполняется в том случае, если выражение условие имеет ненулевое значение. Оператор оперетор2 выполняется тогда, когда выражение условие принимает нулевое значение. Условие else (else оператор2) является необязательным. Вместо операторов оператор1 и оператор2 могут использоваться блоки кода.

Пример 3.11. ifelse.stp
global countread, countnonread
probe kernel.function("vfs_read"),kernel.function("vfs_write")
{
  if (probefunc()=="vfs_read")
    countread ++
  else
    countnonread ++
}
probe timer.s(5) { exit() }
probe end
{
  printf("Общее количество операций чтения VFS %d\n общее количество операций записи VFS %d\n", countread, countnonread)
}
			

Пример 3.11, "ifelse.stp" является сценарием для подсчета количества операций чтения из виртуальной файловой системы (vfs_read) и количества операций записи в виртуальную файловую систему (vfs_write) в течение 5-секундного периода. После запуска сценарий увеличивает значение переменной countread на 1 в том случае, если имя исследуемой функции совпадает с именем vfs_read (как указано при использовании оператора условного перехода if (probefunc()=="vfs_read")); в противном случае он увеличивает значение переменной countnonread (else {countnonread ++}).

Циклы while
Формат:
while (условие)
  оператор
  			

Блок кода, обозначенный с помощью строки оператор исполняется настолько долго, насколько долго выражение условие имеет ненулевое значение. Вместо оператора оператор обычно используется блок кода, который должен изменять определенное значение таким образом, чтобы выражение условие в конце концов приобрело нулевое значение.

Циклы for
Формат
for (инициализация; условие; приращение)
оператор
			
Цикл for является сокращенной версией цикла while. Ниже приведен эквивалентный код, который мог бы быть разработан в случае необходимости использования цикла while:
инициализация
while (условие) {
   оператор
   приращение
}
			

Операторы сравнения

Помимо оператора == ("значение равно"), в сценариях могут быть также использованы следующие операторы:

>=

Значение больше или равно

<=

Значение меньше или равно

!=

Значение не равно

3.3.4. Аргументы командной строки

Сценарий SystemTap также может принимать аргументы, переданные посредством интерфейса командной строки, в случае использовании символа "$" или "@", после которого должен следовать номер аргумента командной строки. Используйте символ "$" в том случае, если вы ожидаете от пользователя ввода целочисленного значения в качестве аргумента командной строки, а символ "@" - если ожидаете ввода строки.

Пример 3.12. commandlineargs.stp
probe kernel.function(@1) { }
probe kernel.function(@1).return { }

Пример 3.12, "commandlineargs.stp" аналогичен Примеру 3.1, "wildcards.stp" за исключением того, что он позволяет вам передавать имя функции ядра ОС, вызов которой необходимо отслеживать, в качестве аргумента командной строки (следующим образом: stap commandlineargs.stp функция ядра). Вы также можете доработать сценарий таким образом, чтобы он принимал множество аргументов командной строки, используя для доступа к этим аргументам обозначения @1, @2 и.т.д. в том порядке, в котором предполагается ввод этих аргументов пользователем.


Следующий раздел : 3.4. Ассоциативные массивы.