Задача для проба: написать пробы которые бы отслеживали посланный от процесса к процессу сигнал в 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])
    }
}

Т.е. имеем параметры запуска:

  1. long - для вывода бактрейсов
  2. pid=numb - для слежения только за указанным pid
  3. sig=numb - для следения только за указанным сигналом
  4. 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

Продолжение следует...

Добавить комментарий

Следующая запись Предыдущая запись