Применительно к DTrace D является языком описания действий в точках трассировки; на нем программируются предикаты и действия. Язык D имеет C-подобный синтаксис с некоторыми элементами синтаксиса AWK.
D-скрипты транслируются в DIF (D Intermediate Format) и эмулируются в ядре при срабатывании датчика (DIF - набор команд RISC, являющийся целевым языком компиляции для libdtrace), что обеспечивает безопасность их выполнения (возможные ошибки, способные дестабилизировать систему, будут обработаны).
Программа на языке D состоит из директив компилятора – прагм (pragmas), объявлений типов, переменных и трансляторов и одной или нескольких компонент (clauses).
Каждая компонента состоит из дескриптора датчика, предиката и описания действия:
дескриптор датчика
предикат
{
действие
}
Дескриптор (идентификатор) датчика включает в себя провайдера, модуль, функцию и имя датчика и записывается таким образом:
probeprov:probemod:probefunc:probename
Не обязательно определять все компоненты дескриптора, также возможно использование специальных символов языка shell.
Пример описания датчика:
# dtrace -l -n "syscall::write*:entry"
ID PROVIDER MODULE FUNCTION NAME
65581 syscall write entry
65781 syscall writev entry
Предикат – это условное выражение. Если он отсутствует в компоненте, считается, что его условие всегда выполняется.
Действия – то, что выполняется после срабатывания датчика и при выполнении условия предиката. Действиями, выполняемыми по умолчанию, являются вывод идентификатора датчика и процессора, а также имени функции и датчика.
Например, в скрипте
syscall::symlink:entry
/basename(copyinstr(arg1))=="motd" /
{
printf("Execname: %s, pid=%d\n", execname, pid);
}
"/basename(copyinstr(arg1))=="motd" /" - предикат, ограничивающий область срабатывания датчика его запуском только при изменении файла /etc/motd, а "printf("Execname: %s, pid=%d\n", execname, pid)" - действие.
Последовательность выполняемых компонентами действий определяется порядком их появления в D-скрипте и временем, когда происходит модификация кода инструментальными средствами.
Поддерживаемые типы переменных – скалярные (объекты фиксированного размера), строки, ассоциативные массивы (с доступом по ключу), встроенные переменные (содержащие контекстно-зависимые данные), внешние переменные (ядра или процесса) и агрегации (именованные структуры, хранящие результат агрегирующей функции, индексируемой кортежами).
Пример использования агрегаций:
hotspot_jni$target:::*-entry
{
JNI_CALLS ++;
@jni_calls[probename] = count();
}
:::END
{
printa("%10@d %s\n", @jni_calls);
printf("\n");
printf("Total number of JNI calls: %d\n", JNI_CALLS);
}
Скрипт считает статистику вызовов методов JNI (Java Native Interface) для каждого датчика, т.к. данные индексируются по одному параметру – probename.
В языке D 32 встроенные переменные, наиболее часто используются следующие:
• probeprov, probemod, probefunc, probename - имена провайдера, модуля, функции и датчика соответственно,
• execname - имя текущего исполняемого модуля;
• pid, ppid - идентификаторы текущего процесса и родителя текущего процесса;
• curpsinfo - структура psinfo для текущего процесса;
• timestamp - время с момента загрузки (в нс);
• args[] - массив аргументов, включающий значения от 0 до значения, равного количеству аргументов, уменьшенному на единицу, и элементы этого массива определяются провайдером.
Кроме глобальных переменных, которые видны всем компонентам и декларируются с использованием синтаксиса С или могут быть объявлены при присваивании неявным образом, имеются thread-local и clause-local переменные любого типа, необходимые для хранения состояния потока (thread-local) или используемые в качестве временных переменных (clause-local). Доступ к ним осуществляется при помощи префиксов self-> и this-> соответственно. Clause-local переменные находятся в области памяти, используемой повторно при выполнении данной компоненты, а thread-local переменные привязывают идентификатор переменной к отдельным областям памяти для каждого потока команд.
Определение области видимости переменных:
syscall::open:entry
{
g = 1;
this->cl = 1;
self->tl = 1;
printf("p: %i, t: %i", pid, tid);
}
dtrace: script '1.d' matched 1 probe
CPU ID FUNCTION:NAME
0 65583 open:entry p: 375, t: 1
0 65583 open:entry p: 375, t: 1
0 65583 open:entry p: 375, t: 1
0 65583 open:entry p: 375, t: 1
0 65583 open:entry p: 565, t: 1
0 65583 open:entry p: 565, t: 1
<…>
Деструктивные операторы
Операторы записывают состояние системы в буфер DTrace и некоторые из них, называемые деструктивными, могут его изменять, поэтому по умолчанию их использование невозможно. Чтобы использовать такие операторы, необходимо указать опцию “-w” или прагму “option destructive”.
Список деструктивных операторов:
• stop - остановить процесс,
• raise - послать сигнал процессу,
• copyout, copyoutstr - записать в память,
• system - вызвать внешнюю программу,
• breakpoint - вызвать отладчик ядра,
• panic - вызвать панику ядра,
• chill - приостановить выполнение процесса на определенное время.
Пример деструктивного действия:
syscall::open:return
/ ! progenyof($pid) && errno != 0 /
{
printf("%s(%i): open(2) failed: ",
execname, pid);
system("/opt/error -s %i", errno);
}
# dtrace -w -s destr.d
dtrace: script 'destr.d' matched 1 probe
dtrace: allowing destructive actions
CPU ID FUNCTION:NAME
0 65584 open:return metacity(554): open(2) failed: sh: line 1:
/opt/error: not found
0 65584 open:return metacity(554): open(2) failed: sh: line 1:
/opt/error: not found
//такого файла действительно не существует