Библиотека сайта 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. Ассоциативные массивы.