Задача для проба: написать пробы которые бы отслеживали посланный от процесса к процессу сигнал в CentOS 7, вывод обеспечит в двух строках: 1 - кто послал кому послал и какой сигнал, 2 - кто принял. И вывод бактрейса хендлера принявшего сигнал.
Первым делом я посетил вот этот перечень готовых функций: https://sourceware.org/systemtap/tapsets/signal.stp.html
Он содержит все необходимы для реализации программы функции: signal.send
+ и signal.handle
+
Учитывая, что необходимо один раз выводить информацию и о посылавшем процессе и о принимающем, то сохраним при посылке данные в массив. Вот тут я и наткнулся на необычное для меня поведение программы, отсутствие поддержки локалных массивов. Это очень неудобно. Второй момент, что-то я не нашел варианта кроме embedded C, прописать структуру, а так как в массив нужно сохранять разнородные данные, для простоты читабельности кода, пришлось для каждого параметра делать отдельный массив.
Ибо инструкция гласит, что:
тип элементов массива в SystemTap определяется типом первого записанного в массив элемента.
Вот проб для сохранения сведений при посылке сигнала. В проб добавлены дополнительные проверки:
probe signal.send {
#флажок того, что pid процесса, посылающего сигнал не находится в списке исключений
found_excluded_pid = 0
#перебираем массив с pid исключениями, если находим, то взводим флажок = 1
foreach(cnt in filter_epid){
if (pid()==filter_epid[cnt]){
found_excluded_pid = 1
break
}
}
#если флажок не 1, то pid не исключен
if (found_excluded_pid == 0) {
# проверим, а не задан ли единственный pid для отслеживания, полезен при режиме -x или когда известен pid
if (filter_pid == 0 || ((filter_pid != 0) && (pid() == filter_pid))){
#и проверим, а случайно не задан ли фильтр для номера сигнала, может нам нужно следить только за одним сигналом
if (filter_sig == 0 || ((filter_sig != 0) && (sig == filter_sig))){
#проверки пройдены, начинаем сохранять, используем для сохранения глобальный счетчик, он будет ключем массива,
# так как возможно мы не успеем обработать массив, когда в него от этого же процесса попадет другой сигнал и данные перезатруться
sc = signal_counter++
si_cn[sig_pid, sig, sc]=sc
# каждый параметр уходит в свой массив, неудобно, но проще метода пока не нашел
si_sn[sc] = sig_name #имя сигнала
si_sp[sc] = sig_pid #pid кому сигнал посылается
si_pp[sc] = pid() #pid посылателя сигнала
si_pn[sc] = pid_name #имя процесса кому сигнал посылается
si_en[sc] = execname() #имя процесса посылателя
si_ppf[sc] = ppfunc()
si_ppf2[sc] = sprint_ubacktrace()
si_gc[sc] = gettimeofday_ns()
}
}
}
}
Все идентификаторы функции описаны по адресу. Т.к это не target переменные, то знак $
перед ними не ставится. Т.е так и пишется sig_name
, sig_pid
и т.д
Теперь опишем принимающий проб, вот он должен и выводить данные накопленные в массиве и добавлять свои:
probe signal.handle {
#пройдемся по массиву хранящему pid и уникальный ключ, ранее описанный глобальный счетчик
foreach([f_pid, sig_f, sc] in si_cn){
#если pid совпал, то берем счетчик, здесь хочу сказать, если посылающий процесс успеет напосылать сигналов
#то подхватится первый попавшийся счетчик для обработчика сигнала
if ((pid() == f_pid) && (sig_f == sig)){
#выводим первую строку с бактрейсом, если он необходим
printf("[%d] Signal %s was send by %s/%d to %s/%d\n",
si_pp[sc], si_sn[sc], si_en[sc], si_pp[sc],
execname(), pid())
if (output_info == 1){
printf("--------------+\n")
printf(" +\n")
printf(" %s------>%s\n", si_ppf[sc], si_ppf2[sc])
}
#выводим вторую строку с бакьрейсом, если он необходим
printf("[%d] Received the signal %s by %d and time %d/1000000000\n", pid(), si_sn[sc], pid(), gettimeofday_ns()-si_gc[sc])
if (output_info == 1){
printf("------------------+\n")
printf(" +\n")
printf(" %s------>%s\n", ppfunc(), sprint_ubacktrace())
}
}
}
}
параметры проба описаны по адресу.
И еще одна часть, это проб begin, он анализирует входные параметры и заполняет фильтры и флажки. Для анализа входных параметров я воспользовался тул типом https://sourceware.org/systemtap/wiki/TipPassingParameters:
probe begin {
printf("Signal analyzer started\n")
if (argc>0) {
for(index=0; index<argc; index++){
if (argv[index+1]=="long"){
output_info = 1
} else if (substr(argv[index+1],0,4)=="pid="){
str_len = strlen(argv[index+1])
nmb_str = substr(argv[index+1], 4, str_len)
filter_pid = strtol(nmb_str, 10)
} else if (substr(argv[index+1],0,4)=="sig="){
str_len = strlen(argv[index+1])
nmb_str = substr(argv[index+1], 4, str_len)
filter_sig = strtol(nmb_str, 10)
} else if (substr(argv[index+1],0,5)=="epid="){
str_len = strlen(argv[index+1])
nmb_str = substr(argv[index+1], 5, str_len)
filter_epid[counter_epid++] = strtol(nmb_str, 10)
} else {
filter_pid = strtol(argv[index+1], 10)
}
}
}
printf("Started with DEBUG %d, PID FILTER %d, SIG FILTER %d\n", output_info, filter_pid, filter_sig)
foreach(cnt in filter_epid){
printf("Excluded pid: %d\n", filter_epid[cnt])
}
}
Т.е. имеем параметры запуска:
- long - для вывода бактрейсов
- pid=numb - для слежения только за указанным pid
- sig=numb - для следения только за указанным сигналом
- epid=numb epid=numb ... - для составления массива pid которые не нужно отслеживать
Пример: в ранее рассматриваемый пример я добавил отправку самому себе SIGKILL
[probes]# stap signals_analyzer.stp -c "./memory"
Signal analyzer started
Started with DEBUG 0, PID FILTER 0, SIG FILTER 0
[13161] Signal SIGUSR1 was send by stapio/13161 to stapio/13163
[13163] Received the signal SIGUSR1 by 13163 and time 35082/1000000000
[13163] Signal SIGKILL was send by memory/13163 to memory/13163
[13163] Received the signal SIGKILL by 13163 and time 5996/1000000000
[13163] Signal SIGCHLD was send by memory/13163 to stapio/13161
[13161] Received the signal SIGCHLD by 13161 and time 50381/1000000000
Waiting for Ctrl-C for stop
Еще примеры запуска:
stap signals_analyzer.stp epid=1 epid=2 epid=100 -c "./memory"
следить за всеми pid кроме 1,2,100
stap signals_analyzer.stp pid=1000
следить кому рассылает сигналы процесс с pid = 1000
stap signals_analyzer.stp sig=9 -c "./memory"
следить только за сигналом SIGKILL
Полный код программы: https://github.com/bayrepo/examples/raw/master/signals_checker.stp
Продолжение следует...