Рейтинг@Mail.ru
[Войти] [Зарегистрироваться]

Наши друзья и партнеры

UnixForum
Беспроводные выключатели nooLite купить дешевый 
компьютер родом из Dhgate.com

Lines Club

Ищем достойных соперников.

Библиотека сайта или "Мой Linux Documentation Project"

Руководство для начинающих пользователей 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. Ассоциативные массивы.


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

Комментарии отсутствуют