Библиотека сайта 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 перед объявлением имени переменной вне обработчиков зондов. Рассмотрите следующий пример:
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 делает вывод о том, что переменная |
|
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
- 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. Отформатированный вывод значений целевых переменных
- $$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.
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.
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.stpglobal 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 также может принимать аргументы, переданные посредством интерфейса командной строки, в случае использовании символа "$" или "@", после которого должен следовать номер аргумента командной строки. Используйте символ "$" в том случае, если вы ожидаете от пользователя ввода целочисленного значения в качестве аргумента командной строки, а символ "@" - если ожидаете ввода строки.
probe kernel.function(@1) { }
probe kernel.function(@1).return { }
Пример 3.12, "commandlineargs.stp" аналогичен Примеру 3.1, "wildcards.stp" за исключением того, что он позволяет вам передавать имя функции ядра ОС, вызов которой необходимо отслеживать, в качестве аргумента командной строки (следующим образом: stap commandlineargs.stp функция ядра). Вы также можете доработать сценарий таким образом, чтобы он принимал множество аргументов командной строки, используя для доступа к этим аргументам обозначения @1, @2 и.т.д. в том порядке, в котором предполагается ввод этих аргументов пользователем.
Следующий раздел : 3.4. Ассоциативные массивы.
