|
Thursday, 14 August. 2008Архитектура и провайдеры DTrace
В системах со встроенной в ядро технологией DTrace (Solaris, FreeBSD, MacOS X, QNX) можно получить полную информацию о том, что происходит с приложением при его работе в ОС. DTrace контролирует основные действия трассируемого приложения – выполнение процессорных команд, чтение из памяти и запись в нее – и при этом динамически модифицирует объект исследования: выключенные датчики не оказывают никакого влияния на производительность и включаются только при явном указании этого.
В отличие от утилиты truss, позволяющей трассировать только системные вызовы и сигналы, DTrace позволяет получить информацию почти обо всех составляющих работы системы, а использование дополнительных статистических утилит можно указать в скрипте на языке D. Truss для сбора информации пользуется файловой системой proc, предназначенной для стандартных средств отладки, поэтому в большинстве случаев трассируемый процесс останавливается, фиксируется нужная информация о его состоянии и процесс запускается заново (чего в DTrace при нормальной работе не наблюдается, хотя методы трассировки могут быть как статическими, так и динамическими). Основные компоненты DTrace:
1. Потребители (consumers) – это процессы, обращающиеся к корневому модулю DTrace, передающие ему при этом спецификации трассировки и обрабатывающие данные, полученные в ее процессе. Для этого используется интерфейс, предоставляемый библиотекой libdtrace, а данные между потребителем и ядром (корневой модуль находится в ядре) передаются с помощью вызова ioctl для псевдоустройства dtrace. Канонический потребитель – одноименная утилита, являющаяся также драйвером для компилятора языка D (драйвер – утилита, управляющая процессом компиляции, например, сс для языка С). Сам компилятор находится в составе библиотеки libdtrace. 2. Корневой модуль DTrace. Он находится в ядре ОС и необходим для доступа к средствам модификации кода, а также буферизации и обработки информации, получаемой от датчиков. Но модуль DTrace не добавляет датчики в код – это задача провайдеров. 3. Провайдеры. По запросу модуля DTrace провайдер определяет в системе точки, куда могут быть установлены датчики, и для каждой найденной точки осуществляет обратный вызов модуля, чтобы создать датчик. Провайдеры являются модулями ядра. 4. Датчики (probes). Каждый датчик определяется провайдером, модулем (в данном контексте – программа, которой он принадлежит), функцией и семантическим именем. ![]() Общая архитектура DTrace После создания датчика модуль DTrace возвращает его ID провайдеру, но код при этом еще не модифицируется. Затем DTracе создает блок управления датчиком (ECB – enabling control block), в котором определяется, что и при каких условиях будет выполнено – т.е. действия и предикаты. Если предикат есть, но его условие не выполняется, DTrace переходит к следующему ECB. Если действие предполагает запись каких-то данных, то они будут сохранены в специальном буфере, привязанному к создавшему блок потребителю (действия не могут содержать явную запись в память ядра и изменение регистров; это можно сделать косвенно, если способ явно указан и у пользователя есть права на выполнение таких действий – например, остановить текущий процесс). После этого провайдеру поступает команда о необходимости включения датчика, а если ECB у датчика уже был, новый блок становится последним из имеющихся у датчика блоков управления. Именно в этот момент провайдер модифицирует систему так, чтобы при срабатывании датчика управление переходило к модулю DTrace и первым аргументом был ID датчика. После срабатывания датчика прерывания на процессоре запрещаются, чтобы дать возможность DTrace выполнить действия, определенные в каждом ECB сработавшего датчика. Когда управление снова переходит к провайдеру, прерывания разрешаются. ![]() ECB, предикаты и действия Пример модификации кода провайдером fbt (платформы SPARC и AMD64) В одной вкладке терминала запускается отладчик модулей ядра mdb и дизассемблируется функция – например, zfs_read (), после выхода из mdb при помощи Ctrl-d в другой вкладке включаются датчики на модуле zfs: #dtrace -m 'zfs { trace(execname);}' В процессе работы DTrace дизассемблирование повторяется. SPARC До включения датчиков: # mdb -k Во время работы DTrace: # mdb -k zfs_read 4: ldx [%i0 0x10], %l1 zfs_read 8: ldx [%l1], %l3 zfs_read 0xc: add %l3, 0x54, %l2 zfs_read 0x10: ldx [%l3 0x10], %l4 zfs_read 0x14: mov %l2, %o0 AMD 64 До включения датчиков: # mdb -k Во время работы DTrace: # mdb -k На платформе AMD64 вызывается программное прерывание – int, а на SPARC выполняется команда безусловного перехода ba,a +offset (offset – ее операнд). В результате прерывания управление перехватывается обработчиком прерываний и передается DTrace, а выполнение команды перехода приводит к исполнению послдующих команд с произвольно заданного адреса (DTrace - dt=0x90ac). Модификация таблицы системных вызовов провайдером syscall # mdb -k В другой вкладке включается датчик для вызова rexit: #dtrace -n 'syscall::rexit:entry' После включения: > sysent+1*0x20::array struct sysent 1 |::print struct sysent Замена системного вызова на dtrace_systrace_syscall позволяет передать управление DTrace, снимающему необходимую для трассировки информацию до и после системного вызова, передав управление системного вызову (в этом примере – rexit) в нужный момент – это метод работы провайдера syscall. В DTrace имеется виртуальная машина, имеющая собственный набор команд RISC, не зависящий от архитектуры, - DIF (D Intermediate Format). D-скрипты транслируются в DIF и эмулируются в ядре при срабатывании датчика. Это требуется для безопасной обработки возможных ошибок, способных нарушить работу системы. Используя ключ -S команды dtrace, можно посмотреть на генерируемые DIF-объекты (DIFO) при выполнении D-скрипта: difo.d syscall::open:entry Провайдер fbt (function boundary tracing) Провайдер трассировки границ функции создает датчики для момента входа во все функции и выхода из всех функций ядра и механизм его реализации привязан к конкретной архитектуре набора команд (см. сравнение SPARC и AMD64). Он позволяет наблюдать за тем, что происходит непосредственно в ядре. Скрипт write.d показывает, какую последовательность вызовов функций ядра генерирует системный вызов write; вывод ограничен только вызовами, происходящими в том же потоке команд, что и системный вызов (для указания ограничения используется thread-local переменная self->follow): syscall::write:entry Провайдер pid С его помощью можно контролировать входы и выходы почти из всех функций любого приложения без дополнительных средств и датчики для этого провайдера создаются динамически, поэтому для просмотра их списка нужно указать приложение, которое будет трассироваться: #dtrace -P pid'$target' -c dtrace -l Например, с помощью этого провайдера можно увидеть, каким образом происходит взаимодействие приложений с библиотеками: lib.d pid$target:$1::entry Результат работы скрипта – информация о том, сколько раз и какие функции вызываются из библиотеки libc при запуске gimp без аргументов. Если считать, что в программе выделение и освобождение памяти происходит только при помощи вызовов malloc() и free(), можно использовать этот провайдер для поиска утечек памяти. malloc.d pid$target:libc:malloc:entry Действие ustack() на входе в malloc позволяет увидеть стек вызовов на момент выделения памяти, следовательно, можно определить, какие выделенные вызовом malloc() участки памяти не были впоследствии освобождены при помощи вызова free(): 0 66081 malloc:return alloc: 847ace0 Провайдер proc Нужен для трассировки системных событий, отображающих процесс исполнения кода (запуск и завершение, отправка и обработка сигналов), потому что управление процессами и сигналами осуществляется не только системными вызовами, но и функциями из стандартной библиотеки С. Список датчиков (из названий понятно их назначение): # dtrace -l -P proc ID PROVIDER MODULE FUNCTION NAME Дерево запуска процессов при вызове определенной команды: calls.d proc:::exec Трассировка удачных запусков (зависимости приложений не учитываются): # dtrace -n 'proc:::exec-success { Провайдер profile Источник событий для profile - прерывания по времени с заданным интервалом. Например, в результате выполнения tick-1s скрипт автоматически закончит работу через 1 секунду, когда сработает датчик tick-1s и действие exit() с целочисленным аргументом, что вызовет срабатывание датчика END (работающего только после того, как отработали все другие датчики) и, следовательно, нормальное завершение работы. Если в скрипте нет подобных конструкций, для просмотра итоговых данных используется Ctrl-C. Обратные ссылки
URI этой записи для создания обратных ссылок (trackback)
Нет обратных ссылок
Комментарии
Показывать комментарии
(Как список | Древовидной структурой)
Отличная статья
Автор не разрешил комментировать эту запись
|
КатегорииБыстрый поискКалендарь
Новостные лентыАвторы |
|||||||||||||||||||||||||||||||||||||||||||||||||
|
|



