Это больше руководство для самого себя по теме systemtap, я это средство отладки недавно обнаружил и решил задокументировать все, что по нему узнал. Поэтапно и постепенно, какие примеры разбирал. Эти меморизы должны будут побороть мой склероз и пригодиться мне в дальнейшем использовании этого мощного средства.
И так, что такое systemtap?
SystemTap — это средство, которое позволяет собирать и анализировать информацию о работающей Linux системе. В отличие от встроенных средств, таких как netstat, ps, top, SystemTap был разработан с целью предоставить больше возможностей для сбора и представления информации.
SystemTap представляет из себя интерфейс командной строки и скриптовый язык программирования.
Системные администраторы могут использовать SystemTap для мониторинга и анализа производительности системы, а разработчики программного обеспечения могут использовать SystemTap для анализа поведения приложения в работающей системе.
В разработке проекта SystemTap участвуют такие компании как Red Hat, IBM, Oracle Corporation, Hitachi.
Установка на CentOS 7
Воспользовавшись командами из мануала, получаю:
yum install systemtap systemtap-runtime
stap-prep
скажу лишь, то что имел вот такие подключенные репозитории на машине:
repo id | repo name |
---|---|
!base/7/x86_64 | CentOS-7 - Base |
!base-debuginfo/x86_64 | CentOS-7 - Debuginfo |
!epel/x86_64 | Extra Packages for Enterprise Linux 7 - x86_64 |
!epel-debuginfo/x86_64 | Extra Packages for Enterprise Linux 7 - x86_64 - Debug |
!home_bayrepo | home:bayrepo (CentOS_7) |
!extras/7/x86_64 | CentOS-7 - Extras |
!updates/7/x86_64 | CentOS-7 - Updates |
Все прошло без особых проблем, все поставилось без дополнительных нареканий.
Инстукция, grep CONFIG_UPROBES /boot/config-$(uname -r)
показала: CONFIG_UPROBES=y
Проверка тестовой программки:
[~]# stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'
Pass 1: parsed user script and 123 library scripts using 227624virt/40728res/3260shr/37660data kb, in 400usr/20sys/732real ms.
Pass 2: analyzed script: 1 probe, 1 function, 4 embeds, 0 globals using 359016virt/173172res/4464shr/169052data kb, in 1830usr/450sys/5222real ms.
Pass 3: using cached /root/.systemtap/cache/ab/stap_ab882d45e2b778be682005a486a8a6be_1675.c
Pass 4: using cached /root/.systemtap/cache/ab/stap_ab882d45e2b778be682005a486a8a6be_1675.ko
Pass 5: starting run.
read performed
Pass 5: run completed in 10usr/50sys/617real ms.
Первая программа
Пример первой программы взят из учебника SystemTap по синтаксису языка. Ниже приведен код программы размещенный в файле first.stp:
1-й вариант:
probe begin {
printf("%d, %s\n", $1, @2)
exit()
}
2-й вариант с точкой с запятой после каждой строки, как в Си:
probe begin {
printf("%d, %s\n", $1, @2);
exit();
}
3-й вариант в одну строку:
probe begin { printf("%d, %s\n", $1, @2); exit(); }
Далее выполняем:
[probes]# stap first.stp "5+5" mystring
10, mystring
Из примера видно, что для обращения к агругментам командной строки необходимо использовать литералы: $1, $2, $3 ... $N для приведения к целому, в нашем случае "5+5"
трактуется как int
и @1, @2 ... для приведения к строке. В нашем случае это "mystring"
. Из примера видно, что литералы можно смешивать, т.е $1, @2, $3. Если обратиться к несуществующему аргументу, то возникнет ошибка.
stap преобразует stp код в С и компилирует его. С результатом промежуточным преобразования можно ознакомиться, запустив команду с параметром -k
- stap -k first.stp "5+5" mystring
В первую очередь хотелось бы описать userspace probing
Начнем с того, что systemtap позволяет работать как с функциями ядра, так и с userspace. Задокументирую в первую очередь userspace, как более понятную для меня область.
Описание синтаксиса я приводить не буду, при желании его легко выудить из мануала. Все что необходимо разобрать, буду разбирать по мере необходимости при написании тестовых пробов.
begin и end
Примеры использования:
process.begin
process("PATH").begin
process(PID).begin
process.thread.begin
process("PATH").thread.begin
process(PID).thread.begin
process.end
process("PATH").end
process(PID).end
process.thread.end
process("PATH").thread.end
process(PID).thread.end
.begin
- вызывается когда процесс описанный PID или PATH создан. Если ни PID ни PATH не заданы, то .begin
вызывается для всех создающихся и отпочковавшихся процессов.
.thread.begin
то же самое, что и выше, только для потоков созданных в рамках указанного процесса.
.end
и .thread.end
вызываются, когда процесс умирает.
В документации написанно, что именно когда умирает , а не умер. Т.е пока он еще существует. Я так понимаю.
Я для понимания и осознания наваял вот такую прожку и собрал ее gdb -o memory memory.c -ggdb3
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void memory_fill(char *src, char *dest, int len){
char buff[1024] = {0};
memcpy((char *)buff, src, len);
memcpy(dest, buff, len);
}
int main(){
char test[1024] = {0};
char *data = (char *)malloc(sizeof test);
if (!data){
printf("Ooops\n");
return 1;
}
memory_fill(test, data, sizeof test);
free(data);
return 0;
}
Не несет абсолютно никакой смысловой нагрузки, сделана для исследования. И первым делом сделаем probe для отслеживания ее запуска:
#! /usr/bin/env stap
# Здесь я прошу при детекте вызова программы вывести ее pid
probe process("./memory").begin {
printf("Test process started %d\n", pid())
}
# Здесь то же самое, только при завершении программы
probe process("./memory").end {
printf("Test process ended %d\n", pid())
}
#А здесь говорю висеть и ждать Ctrl+C, если добавлю exit() то получу мгновенный останов
probe begin {
printf("Waiting for even or Ctrl+C\n")
}
Запускаю, вариант 1:
[~]# stap malloc.stp -c "./memory"
Waiting for even or Ctrl+C
Test process started 14360
Test process ended 14360
Весьма недурно, отследил такую быстро завершающуюся программу. А теперь запущу без параметров и в другом терминале буду запускать программу:
[~]# stap malloc.stp
Waiting for even or Ctrl+C
Test process started 14435
Test process ended 14435
Test process started 14436
Test process ended 14436
Test process started 14437
Test process ended 14437
^C
Неплохо, до прерывания были выловлены все запуски.
.syscall и .function и .statement
Если заменить .begin
на .syscall
, а .end
на .syscall.return
, то получим probe для начала и конца системного вызова, где в переменной $syscall
можно плучить номер системного вызова, а в первых шести аргументах его параметры: $arg1
... $arg6
.
.function
, так же аналогичен .begin
, а .function.return
= .end
. Только в этом случае, еще задается имя функции:
process("PATH").function("NAME")
process("PATH").statement("*@FILE.c:123")
process("PATH").function("*").return
process("PATH").function("myfun").label("foo")
Разберем код для извлечения имен функций из программы, первый пример для программы собранной с -g:
[~]# stap -e 'probe process("./memory").function("*").call { log (probefunc()." ".$$parms)}' -c './memory'
_start
__libc_csu_init
_init
frame_dummy
register_tm_clones
main
memory_fill src=0x7fff10cba010 dest=0x1d37010 len=0x400
__do_global_dtors_aux
deregister_tm_clones
_fini
и второй пример, для этой же программы, но собранной без -g:
[~]# stap -e 'probe process("./memory").function("*").call { log (probefunc()." ".$$parms)}' -c './memory'
_start
__libc_csu_init
_init
frame_dummy
register_tm_clones
main
memory_fill
__do_global_dtors_aux
deregister_tm_clones
_fini
Идентично!!!
В заключении главы приведу список в стиле, что пробировать, что печатать: готовые функции и идентификаторы.
tid()
id текущего потока.pid()
id текущего процесса текущего потока.uid()
id текущего пользователя.execname()
имя текущего процесса.cpu()
номер текущего процессора.gettimeofday_s()
аналог time().get_cycles()
число циклов.pp()
указатель на текущий probe, который выполняется.ppfunc()
имя функции, в которую текущий probe был помещен, если доступно.$$vars
список перемнных локальных в области видимости.print_backtrace()
распечатать kernel backtrace.print_ubacktrace()
распечатать user-space backtrace.
Ну и ссылка на документацию обязательно к прочтению, хоть и на английском
Вырезка оттуда, думаю важная, как итог главы:
begin
The startup of the systemtap session.end
The end of the systemtap session.kernel.function("sys_open")
The entry to the function named sys_open in the kernel.syscall.close.return
The return from the close system call.module("ext3").statement(0xdeadbeef)
The addressed instruction in the ext3 filesystem driver.timer.ms(200)
A timer that fires every 200 milliseconds.timer.profile
A timer that fires periodically on every CPU.perf.hw.cache_misses
A particular number of CPU cache misses have occurred.procfs("status").read
A process trying to read a synthetic file.process("a.out").statement("*@main.c:200")
Line 200 of the a.out program.