Мониторинг параллелизма потоков системных вызовов с помощью ptrace - PullRequest
0 голосов
/ 27 февраля 2020

Моя цель состоит в том, чтобы контролировать все потоки в процессе, чтобы каждый раз знать, что процесс вызывает write системный вызов в течение 3 секунд.

Для этого я использую ptrace и присоединяюсь ко всем потокам в процесс, определите таймаут для потоков, которые не используют syscall.

вот код C

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <signal.h>
#include <sys/reg.h>
#include <sys/types.h>
#include <string.h>

volatile int timeout_g =0;
volatile int stop_loop_g = 0;

struct Node
{

    void  *data;

    struct Node *next;
};


void push(struct Node** head_ref, void *new_data, size_t data_size)
{
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

    new_node->data  = malloc(data_size);
    new_node->next = (*head_ref);

    int i;
    for (i=0; i<data_size; i++)
        *(char *)(new_node->data + i) = *(char *)(new_data + i);

    (*head_ref)    = new_node;
}

struct Node* get_tid_list(int pid)
{
    struct Node* tids_list = NULL;
    static const char task_path[] = "/proc/%d/task";
    char procdir[sizeof(task_path)+sizeof(int) *3];
    DIR * dir;
    int tid;
    if(sprintf(procdir, task_path, pid)>0 && ( dir = opendir(procdir))!= NULL)
    {
        struct dirent *de;
        while((de = readdir(dir))!=NULL)
        {
            if(de->d_fileno ==0)
                continue;
            char dummy ;
            if (sscanf(de->d_name, "%d%c" , &tid, &dummy)!=1)
            {
                continue;
            }

            push (&tids_list, &tid, sizeof(int));
        }
    }
    return tids_list;
}

int wait_signal_or_timeout (int tid)
{
    struct timespec timeout;
    timeout.tv_sec = 2;
    timeout.tv_nsec =0;
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, & set , NULL);
    int ret = sigtimedwait(&set , NULL, &timeout);

    if(ret == SIGCHLD)
    {

        goto exit;
    }
    if(ret == SIGINT)
    {
        stop_loop_g = 1;

        goto exit;
    }
    if(errno == EAGAIN)
    {
        timeout_g = 1 ;

        goto exit;
    }
    //printf("error , tid = %d \n",tid);
    printf("error , ret= %d  , errno = %s\n",ret, strerror(errno));


    exit:
        sigemptyset(&set);
        sigprocmask(SIG_SETMASK, &set, NULL);
        if(timeout_g  || stop_loop_g)
            return -1;
        else
            return 0;
}
void main(int argv, char *argc[])
{

    int pid = atoi(argc[1]);

    int status ;
    struct Node* tids_list = get_tid_list(pid);

    while(tids_list != NULL && !stop_loop_g)
    {

        int tid = *(int*)tids_list->data;
        //printf("tid = %d \n",tid);
        if(ptrace(PTRACE_ATTACH, tid, NULL,NULL) == -1 )
        {
            printf("error attach tid = %d \n", tid );
            tids_list = tids_list->next;
            continue;
        }
        if(waitpid(tid, &status, __WALL)==-1)
        {
            printf("error waitpid tid = %d \n", tid );
            tids_list = tids_list->next;
            continue;
        }
        timeout_g = 0;
        time_t finish_time = time(NULL) +3;//check during 3 sec.
        int flag =0;
        int  use_syscall=0;
        while ((long)time(NULL) -(long)finish_time <0 && !timeout_g)
        {
            if(ptrace(PTRACE_SYSCALL, tid, NULL, NULL) == -1)
            {
                printf("error. tid = %d .\n",tid);
                break;
            }
            if(wait_signal_or_timeout(tid) ==-1)
                break;

            if(flag ==0)//enter syscall
            {
                flag = 1;
                int command = ptrace(PTRACE_PEEKUSER,tid, 8 * ORIG_RAX,NULL);
                if(command == SYS_write)
                {
                    use_syscall =1;
                    printf("tid = %d , call to WRITE syscall = %d \n",tid, command);

                }
            }
            else //exit from syscall
            {
                flag = 0;
            }
        }
        if(use_syscall)
        {
            printf("tid = %d , use write syscall \n",tid);
        }
        else
        {
            printf("tid = %d , didn't use write syscall \n",tid);
        }

        if(ptrace(PTRACE_DETACH, tid, NULL , NULL) <0)
        {
            //can't detach from tid , maybe because tid not stop

            syscall(SYS_kill, tid , SIGSTOP); //send sigstop so we can detach from tid
            if(waitpid(tid , &status, __WALL) == -1)
            {
                printf("error while send SIGSTOP \n");
            }
            if(ptrace(PTRACE_DETACH , tid, NULL , NULL) <0)
            {
                printf("ERROR detach again ! \n");
            }
            syscall(SYS_kill, tid , SIGCONT);
        }
        tids_list = tids_list->next;
    }
}

Вот тестовая программа

//compile with gcc -othreads threads.c -lpthread

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>  
#include <pthread.h> 


void * write_each_sec(void *vargp) 
{ 
    printf("start write_each_sec \n");
    while(1)
    {   
        sleep(1); 
        printf("a\n"    );
    }

    return NULL; 
} 



void * only_sleep(void *vargp) 
{ 
    printf("start only_sleep \n");
    while(1)
    {   
        sleep(1);
    }

    return NULL; 
} 

int main() 
{ 
    pthread_t thread_id; 
    int i ;
    for(i= 0; i<30;i++)
    {
        if(i%2==0)
            pthread_create(&thread_id, NULL, write_each_sec, NULL); 
        else
            pthread_create(&thread_id, NULL, only_sleep, NULL); 
    }

    pthread_join(thread_id, NULL); 

    exit(0); 
}

Создание тестовой программы половина потоков, использующих запись syscall, и половина потоков, не использующих запись syscall.

Мой код работает хорошо, но у него есть две проблемы:

  1. На данный момент код не является параллельным проверить код потоком. Как я могу изменить этот код на параллелизм?

  2. Поскольку я определяю тайм-аут для потоков, которые не вызывают syscall по таймауту, поэтому я могу вызвать PTRACE_DETACH, когда tid не останавливается, Согласно man ptrace(2): If the tracee is running when the tracer wants to detach it, the usual solution is to send SIGSTOP (using tgkill(2), to make sure it goes to the correct thread), wait for the tracee to stop in signal delivery-stop for SIGSTOP and then detach it (suppressing SIGSTOP injection). Проблема в том, что я не могу отправить SIGSTOP на tid, который останавливает весь процесс (я не хочу этого делать), но на данный момент я не нашел никакого умного способа отсоединиться от Тид, если это не остановить. Есть какой нибудь умный способ?

...