Подтвердить что ты не робот

Как контролировать внешний процесс для событий по его PID в C?

Есть ли какая-нибудь библиотека, которая получила какую-то функцию, позволяет контролировать внешний процесс для событий по pid_t? Я имею в виду, отслеживать, вышел ли внешний процесс или создал ли он один или несколько дочерних процессов (с fork) или стал другим исполняемым изображением (через семейный вызов exec или posix_spawn), или был передан сигнал Unix к нему.

ИЗМЕНИТЬ

Мне нужно что-то, что не мешает исполнению программы, которая контролируется. Таким образом, я не должен использовать ptrace, так как он останавливает процесс, который контролируется, когда он испускает какой-либо сигнал, и необходимо возобновить процесс, когда это произойдет.

4b9b3361

Ответ 1

Запустите целевой бинарник, используя библиотеку предварительной загрузки, которая ловит fork(). Пока все дочерние процессы также используют библиотеку предварительной загрузки, вы увидите все локальные дочерние процессы, независимо от того, как они выполняются.

Вот пример реализации.

Сначала заголовок forkmonitor.h. Он определяет сообщения, переданные из библиотеки предварительной загрузки, в процесс мониторинга:

#ifndef   FORKMONITOR_H
#define   FORKMONITOR_H

#define   FORKMONITOR_ENVNAME "FORKMONITOR_SOCKET"

#ifndef   UNIX_PATH_MAX
#define   UNIX_PATH_MAX 108
#endif

#define TYPE_EXEC       1   /* When a binary is executed */
#define TYPE_DONE       2   /* exit() or return from main() */
#define TYPE_FORK       3
#define TYPE_VFORK      4
#define TYPE_EXIT       5   /* _exit() or _Exit() */
#define TYPE_ABORT      6   /* abort() */

struct message {
    pid_t          pid;     /* Process ID */
    pid_t          ppid;    /* Parent process ID */
    pid_t          sid;     /* Session ID */
    pid_t          pgid;    /* Process group ID */
    uid_t          uid;     /* Real user ID */
    gid_t          gid;     /* Real group ID */
    uid_t          euid;    /* Effective user ID */
    gid_t          egid;    /* Effective group ID */
    unsigned short len;     /* Length of data[] */
    unsigned char  type;    /* One of the TYPE_ constants */
    char           data[0]; /* Optional payload, possibly longer */
};

#endif /* FORKMONITOR_H */

Переменная среды FORKMONITOR_SOCKET (названная макросом FORKMONITOR_ENVNAME выше) указывает на добавление сокета к пакету datagram домена Unix в процесс мониторинга. Если не определено или пусто, сообщения мониторинга не отправляются.

Вот сама библиотека, libforkmonitor.c. Обратите внимание, что я упростил код совсем немного, оставив многопоточную инициализацию (поскольку для библиотеки редко можно вызвать любую из перехваченных функций и даже реже сделать это из нескольких потоков). Было бы лучше использовать атомные встроенные модули (__sync_bool_compare_and_swap()) для обновления указателя функции и атомарного getter (__sync_fetch_and_or (, 0)) для извлечения указателя функции, чтобы избежать проблем с wonky-библиотеками. (Это вполне безопасно для многопоточных программ, поскольку указатели будут изменены только до выполнения main().)

#define  _POSIX_C_SOURCE 200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <dlfcn.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"

static pid_t (*actual_fork)(void)  = NULL;
static pid_t (*actual_vfork)(void) = NULL;
static void  (*actual_abort)(void) = NULL;
static void  (*actual__exit)(int)  = NULL;
static void  (*actual__Exit)(int)  = NULL;
static int     commfd = -1;

#define MINIMUM_COMMFD  31

static void notify(const int type, struct message *const msg, const size_t extra)
{
    const int    saved_errno = errno;

    msg->pid  = getpid();
    msg->ppid = getppid();
    msg->sid  = getsid(0);
    msg->pgid = getpgrp();
    msg->uid  = getuid();
    msg->gid  = getgid();
    msg->euid = geteuid();
    msg->egid = getegid();
    msg->len  = extra;
    msg->type = type;

    /* Since we don't have any method of dealing with send() errors
     * or partial send()s, we just fire one off and hope for the best. */
    send(commfd, msg, sizeof (struct message) + extra, MSG_EOR | MSG_NOSIGNAL);

    errno = saved_errno;
}

void libforkmonitor_init(void) __attribute__((constructor));
void libforkmonitor_init(void)
{
    const int saved_errno = errno;
    int       result;

    /* Save the actual fork() call pointer. */
    if (!actual_fork)
        *(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");

    /* Save the actual vfork() call pointer. */
    if (!actual_vfork)
        *(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");

    /* Save the actual abort() call pointer. */
    if (!actual_abort)
        *(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");

    /* Save the actual _exit() call pointer. */
    if (!actual__exit)
        *(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
    if (!actual__exit)
        *(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");

    /* Save the actual abort() call pointer. */
    if (!actual__Exit)
        *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
    if (!actual__Exit)
        *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");

    /* Open an Unix domain datagram socket to the observer. */
    if (commfd == -1) {
        const char *address;

        /* Connect to where? */
        address = getenv(FORKMONITOR_ENVNAME);
        if (address && *address) {
            struct sockaddr_un addr;

            memset(&addr, 0, sizeof addr);
            addr.sun_family = AF_UNIX;
            strncpy(addr.sun_path, address, sizeof addr.sun_path - 1);

            /* Create and bind the socket. */
            commfd = socket(AF_UNIX, SOCK_DGRAM, 0);
            if (commfd != -1) {
                if (connect(commfd, (const struct sockaddr *)&addr, sizeof (addr)) == -1) {
                    /* Failed. Close the socket. */
                    do {
                        result = close(commfd);
                    } while (result == -1 && errno == EINTR);
                    commfd = -1;
                }
            }

            /* Move commfd to a high descriptor, to avoid complications. */
            if (commfd != -1 && commfd < MINIMUM_COMMFD) {
                const int newfd = MINIMUM_COMMFD;
                do {
                    result = dup2(commfd, newfd);
                } while (result == -1 && errno == EINTR);
                if (!result) {
                    do {
                        result = close(commfd);
                    } while (result == -1 && errno == EINTR);
                    commfd = newfd;
                }
            }
        }
    }

    /* Send an init message, listing the executable path. */
    if (commfd != -1) {
        size_t          len = 128;
        struct message *msg = NULL;

        while (1) {
            ssize_t n;

            free(msg);
            msg = malloc(sizeof (struct message) + len);
            if (!msg) {
                len = 0;
                break;
            }

            n = readlink("/proc/self/exe", msg->data, len);
            if (n > (ssize_t)0 && (size_t)n < len) {
                msg->data[n] = '\0';
                len = n + 1;
                break;
            }

            len = (3 * len) / 2;
            if (len >= 65536U) {
                free(msg);
                msg = NULL;
                len = 0;
                break;
            }
        }

        if (len > 0) {
            /* INIT message with executable name */
            notify(TYPE_EXEC, msg, len);
            free(msg);
        } else {
            /* INIT message without executable name */
            struct message msg2;
            notify(TYPE_EXEC, &msg2, sizeof msg2);
        }
    }

    /* Restore errno. */
    errno = saved_errno;
}

void libforkmonitor_done(void) __attribute__((destructor));
void libforkmonitor_done(void)
{
    const int saved_errno = errno;
    int       result;

    /* Send an exit message, no data. */
    if (commfd != -1) {
        struct message msg;
        notify(TYPE_DONE, &msg, sizeof msg);
    }

    /* If commfd is open, close it. */
    if (commfd != -1) {
        do {
            result = close(commfd);
        } while (result == -1 && errno == EINTR);
    }

    /* Restore errno. */
    errno = saved_errno;
}

/*
 * Hooked C library functions.
*/

pid_t fork(void)
{
    pid_t result;

    if (!actual_fork) {
        const int saved_errno = errno;

        *(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");
        if (!actual_fork) {
            errno = EAGAIN;
            return (pid_t)-1;
        }

        errno = saved_errno;
    }

    result = actual_fork();
    if (!result && commfd != -1) {
        struct message msg;
        notify(TYPE_FORK, &msg, sizeof msg);
    }

    return result;
}

pid_t vfork(void)
{
    pid_t result;

    if (!actual_vfork) {
        const int saved_errno = errno;

        *(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");
        if (!actual_vfork) {
            errno = EAGAIN;
            return (pid_t)-1;
        }

        errno = saved_errno;
    }

    result = actual_vfork();
    if (!result && commfd != -1) {
        struct message msg;
        notify(TYPE_VFORK, &msg, sizeof msg);
    }

    return result;
}

void _exit(const int code)
{
    if (!actual__exit) {
        const int saved_errno = errno;
        *(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
        if (!actual__exit)
            *(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");
        errno = saved_errno;
    }

    if (commfd != -1) {
        struct {
            struct message  msg;
            int             extra;
        } data;

        memcpy(&data.msg.data[0], &code, sizeof code);
        notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
    }

    if (actual__exit)
        actual__exit(code);

    exit(code);
}

void _Exit(const int code)
{
    if (!actual__Exit) {
        const int saved_errno = errno;
        *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
        if (!actual__Exit)
            *(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");
        errno = saved_errno;
    }

    if (commfd != -1) {
        struct {
            struct message  msg;
            int             extra;
        } data;

        memcpy(&data.msg.data[0], &code, sizeof code);
        notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
    }

    if (actual__Exit)
        actual__Exit(code);

    exit(code);
}

void abort(void)
{
    if (!actual_abort) {
        const int saved_errno = errno;
        *(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");
        errno = saved_errno;
    }

    if (commfd != -1) {
        struct message msg;
        notify(TYPE_ABORT, &msg, sizeof msg);
    }

    actual_abort();
    exit(127);
}

Функция libforkmonitor_init() вызывается автоматически компоновщиком времени выполнения до вызова процесса main() и вызывается libforkmonitor_done(), когда процесс возвращается из main() или вызывает exit().

В libforkmonitor_init() открывается сеанс дейтаграммы домена Unix для процесса мониторинга и отправляется его учетные данные и путь к текущему исполняемому файлу. Каждый дочерний процесс (до тех пор, пока библиотека предварительной загрузки все еще загружается) выполняет это после загрузки, поэтому нет необходимости вообще перехватывать функции exec*() или posix_spawn*() или 'popen() `и т.д.

Функции библиотеки C fork() и vfork() перехватываются. Эти перехваты необходимы, чтобы поймать случаи, когда исходная программа вилки создает подчиненные процессы без выполнения каких-либо других двоичных файлов. (По крайней мере, библиотека GNU C использует fork() внутренне, поэтому они поймают popen(), posix_spawn() и т.д.).

Кроме того, функции библиотеки C _exit(), _exit() и abort() также перехватываются. Я добавил их, потому что некоторые двоичные файлы, особенно Dash, любят использовать _exit(), и я подумал, что было бы неплохо поймать все формы обычных выходов. (Смерть из-за сигналов не обнаружена, и если двоичный файл выполняет другой двоичный код, вы получите только новое сообщение EXEC. Обратите внимание на идентификаторы процесса и родительского процесса.)

Вот простая программа мониторинга forkmonitor.c:

#define  _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"

static volatile sig_atomic_t  done = 0;

static void done_handler(const int signum)
{
    if (!done)
        done = signum;
}

static int catch_done(const int signum)
{
    struct sigaction  act;

    sigemptyset(&act.sa_mask);
    act.sa_handler = done_handler;
    act.sa_flags = 0;

    if (sigaction(signum, &act, NULL) == -1)
        return errno;

    return 0;
}

static const char *username(const uid_t uid)
{
    static char    buffer[128];
    struct passwd *pw;

    pw = getpwuid(uid);
    if (!pw)
        return NULL;

    strncpy(buffer, pw->pw_name, sizeof buffer - 1);
    buffer[sizeof buffer - 1] = '\0';

    return (const char *)buffer;
}

static const char *groupname(const gid_t gid)
{
    static char   buffer[128];
    struct group *gr;

    gr = getgrgid(gid);
    if (!gr)
        return NULL;

    strncpy(buffer, gr->gr_name, sizeof buffer - 1);
    buffer[sizeof buffer - 1] = '\0';

    return (const char *)buffer;
}

int main(int argc, char *argv[])
{
    const size_t    msglen = 65536;
    struct message *msg;
    int             socketfd, result;
    const char     *user, *group;

    if (catch_done(SIGINT) || catch_done(SIGQUIT) || catch_done(SIGHUP) ||
        catch_done(SIGTERM) || catch_done(SIGPIPE)) {
        fprintf(stderr, "Cannot set signal handlers: %s.\n", strerror(errno));
        return 1;
    }

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s MONITOR-SOCKET-PATH\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program outputs events reported by libforkmonitor\n");
        fprintf(stderr, "to Unix domain datagram sockets at MONITOR-SOCKET-PATH.\n");
        fprintf(stderr, "\n");
        return 0;
    }

    msg = malloc(msglen);
    if (!msg) {
        fprintf(stderr, "Out of memory.\n");
        return 1;
    }

    socketfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (socketfd == -1) {
        fprintf(stderr, "Cannot create an Unix domain datagram socket: %s.\n", strerror(errno));
        return 1;
    }

    {
        struct sockaddr_un  addr;
        size_t              len;

        if (argv[1])
            len = strlen(argv[1]);
        else
            len = 0;
        if (len < 1 || len >= UNIX_PATH_MAX) {
            fprintf(stderr, "%s: Path is too long (max. %d characters)\n", argv[1], UNIX_PATH_MAX - 1);
            return 1;
        }

        memset(&addr, 0, sizeof addr);
        addr.sun_family = AF_UNIX;
        memcpy(addr.sun_path, argv[1], len + 1); /* Include '\0' at end */

        if (bind(socketfd, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
            fprintf(stderr, "Cannot bind to %s: %s.\n", argv[1], strerror(errno));
            return 1;
        }
    }

    printf("Waiting for connections.\n");
    printf("\n");

    /* Infinite loop. */
    while (!done) {
        ssize_t  n;

        n = recv(socketfd, msg, msglen, 0);
        if (n == -1) {
            const char *const errmsg = strerror(errno);
            fprintf(stderr, "%s.\n", errmsg);
            fflush(stderr);
            break;
        }

        if (msglen < sizeof (struct message)) {
            fprintf(stderr, "Received a partial message; discarded.\n");
            fflush(stderr);
            continue;
        }

        switch (msg->type) {
        case TYPE_EXEC:
            printf("Received an EXEC message:\n");
            break;
        case TYPE_DONE:
            printf("Received a DONE message:\n");
            break;
        case TYPE_FORK:
            printf("Received a FORK message:\n");
            break;
        case TYPE_VFORK:
            printf("Received a VFORK message:\n");
            break;
        case TYPE_EXIT:
            printf("Received an EXIT message:\n");
            break;
        case TYPE_ABORT:
            printf("Received an ABORT message:\n");
            break;
        default:
            printf("Received an UNKNOWN message:\n");
            break;
        }

        if (msg->type == TYPE_EXEC && (size_t)n > sizeof (struct message)) {
            if (*((char *)msg + n - 1) == '\0')
                printf("\tExecutable:        '%s'\n", (char *)msg + sizeof (struct message));
        }

        printf("\tProcess ID:         %d\n", (int)msg->pid);
        printf("\tParent process ID:  %d\n", (int)msg->ppid);
        printf("\tSession ID:         %d\n", (int)msg->sid);
        printf("\tProcess group ID:   %d\n", (int)msg->pgid);

        user = username(msg->uid);
        if (user)
            printf("\tReal user:         '%s' (%d)\n", user, (int)msg->uid);
        else
            printf("\tReal user:          %d\n", (int)msg->uid);

        group = groupname(msg->gid);
        if (group)
            printf("\tReal group:        '%s' (%d)\n", group, (int)msg->gid);
        else
            printf("\tReal group:         %d\n", (int)msg->gid);

        user = username(msg->euid);
        if (user)
            printf("\tEffective user:    '%s' (%d)\n", user, (int)msg->euid);
        else
            printf("\tEffective user:     %d\n", (int)msg->euid);

        group = groupname(msg->egid);
        if (group)
            printf("\tEffective group:   '%s' (%d)\n", group, (int)msg->egid);
        else
            printf("\tEffective group:    %d\n", (int)msg->egid);

        printf("\n");
        fflush(stdout);
    }

    do {
        result = close(socketfd);
    } while (result == -1 && errno == EINTR);

    unlink(argv[1]);

    return 0;
}

Требуется один параметр командной строки - адрес сокета домена Unix. Это должен быть абсолютный путь к файловой системе.

Вы можете остановить программу мониторинга с помощью сигналов INT (Ctrl+C), HUP, QUIT и TERM.

Скомпилируйте библиотеку, используя

gcc -W -Wall -O3 -fpic -fPIC -c libforkmonitor.c
gcc -shared -Wl,-soname,libforkmonitor.so libforkmonitor.o -ldl -o libforkmonitor.so

и программу мониторинга с помощью

gcc -W -Wall -O3 forkmonitor.c -o forkmonitor

В одном окне терминала сначала запустите forkmonitor:

./forkmonitor "$PWD/commsocket"

В другом окне терминала в том же каталоге запустите контролируемую команду, предварительно предварительно загрузив библиотеку libforkmonitor.so и указав сокет для монитора:

env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" command args...

Обратите внимание, что поскольку это использует переменные среды LD_PRELOAD и FORKMONITOR_SOCKET, дочерние процессы игнорируются, если их родительский язык модифицирует среду (удаление двух переменных среды) и при выполнении двоичных файлов setuid или setgid. Это ограничение можно избежать, устраняя переменные окружения и жестко кодируя их.

Средство компоновки во время выполнения не будет предварительно загружать библиотеки для двоичных файлов setuid или setgid, если библиотека не находится в одном из стандартных библиотечных каталогов, а также отмечена setgid.

Добавление имени библиотеки в /etc/ld.so.preload будет предварительно загружать библиотеку для всех двоичных файлов, но вы, вероятно, должны добавить механизм в libforkmonitor_init(), который ограничивает мониторинг желаемыми исполняемыми файлами и/или конкретным реальным пользователем (так как эффективный пользователь меняет, когда запуск двоичного файла setuid).

Например, когда я запускаю

env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" sh -c 'date ; ls -laF'

выход мониторинга (анонимный):

Received an EXEC message:
Executable:        'bin/dash'
Process ID:         11403
Parent process ID:  9265
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received a FORK message:
Process ID:         11404
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received an EXEC message:
Executable:        'bin/date'
Process ID:         11404
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received a DONE message:
Process ID:         11404
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received a FORK message:
Process ID:         11405
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received an EXEC message:
Executable:        'bin/ls'
Process ID:         11405
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received a DONE message:
Process ID:         11405
Parent process ID:  11403
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Received an EXIT message:
Process ID:         11403
Parent process ID:  9265
Session ID:         9265
Process group ID:   11403
Real user:         'username' (1000)
Real group:        'username' (1000)
Effective user:    'username' (1000)
Effective group:   'username' (1000)

Это очень легкое решение для мониторинга дерева процессов. Помимо запуска, выхода и вызова одной из перехваченных функций (fork(), vfork(), _exit(), _exit(), abort()) выполнение программы не будет затронуто вообще. Поскольку библиотека настолько легка, что даже затронутые будут затронуты очень-очень маленькой суммой; вероятно, недостаточно для надежного измерения.

Очевидно, что возможно перехватить другие функции и/или использовать двустороннюю связь, "приостанавливая" выполнение перехваченной функции до тех пор, пока приложение мониторинга не ответит.

В целом есть некоторые подводные камни, особенно связанные с процессами setuid/setgid и процессами, которые генерируют новую среду (исключая переменные среды LD_PRELOAD и FORKMONITOR_SOCKET), но их можно обойти, если доступны привилегии суперпользователя.

Надеюсь, вы найдете эту информацию. Вопросы?

Ответ 2

Если вы можете запускать от имени пользователя root, то вы можете использовать события обработки интерфейса netlink:

http://bewareofgeek.livejournal.com/2945.html

Я просто скомпилировал его на Fedora 17 x86_64, и это дает мне следующее:

[[email protected] yotest]# ./proc
set mcast listen ok
fork: parent tid=2358 pid=2358 -> child tid=21007 pid=21007
exec: tid=21007 pid=21007
fork: parent tid=21007 pid=21007 -> child tid=21008 pid=21008
fork: parent tid=21007 pid=21007 -> child tid=21009 pid=21009
fork: parent tid=21007 pid=21007 -> child tid=21010 pid=21010
fork: parent tid=21007 pid=21007 -> child tid=21011 pid=21011
exec: tid=21010 pid=21010
exec: tid=21008 pid=21008
exec: tid=21011 pid=21011
exec: tid=21009 pid=21009
exit: tid=21008 pid=21008 exit_code=0
fork: parent tid=21010 pid=21010 -> child tid=21012 pid=21012
exit: tid=21009 pid=21009 exit_code=0
exec: tid=21012 pid=21012
exit: tid=21012 pid=21012 exit_code=0
exit: tid=21010 pid=21010 exit_code=0
exit: tid=21011 pid=21011 exit_code=0
exit: tid=21007 pid=21007 exit_code=0

Вам нужно будет фильтровать конкретные интересующие вас сообщения, но вы можете легко сделать это в инструкции switch в строке 107.

В целях сохранения:

#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <linux/cn_proc.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/*
 * connect to netlink
 * returns netlink socket, or -1 on error
 */
static int nl_connect()
{
    int rc;
    int nl_sock;
    struct sockaddr_nl sa_nl;

    nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
    if (nl_sock == -1) {
        perror("socket");
        return -1;
    }

    sa_nl.nl_family = AF_NETLINK;
    sa_nl.nl_groups = CN_IDX_PROC;
    sa_nl.nl_pid = getpid();

    rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
    if (rc == -1) {
        perror("bind");
        close(nl_sock);
        return -1;
    }

    return nl_sock;
}

/*
 * subscribe on proc events (process notifications)
 */
static int set_proc_ev_listen(int nl_sock, bool enable)
{
    int rc;
    struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
        struct nlmsghdr nl_hdr;
        struct __attribute__ ((__packed__)) {
            struct cn_msg cn_msg;
            enum proc_cn_mcast_op cn_mcast;
        };
    } nlcn_msg;

    memset(&nlcn_msg, 0, sizeof(nlcn_msg));
    nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
    nlcn_msg.nl_hdr.nlmsg_pid = getpid();
    nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;

    nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
    nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
    nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);

    nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;

    rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
    if (rc == -1) {
        perror("netlink send");
        return -1;
    }

    return 0;
}

/*
 * handle a single process event
 */
static volatile bool need_exit = false;
static int handle_proc_ev(int nl_sock)
{
    int rc;
    struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
        struct nlmsghdr nl_hdr;
        struct __attribute__ ((__packed__)) {
            struct cn_msg cn_msg;
            struct proc_event proc_ev;
        };
    } nlcn_msg;

    while (!need_exit) {
        rc = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
        if (rc == 0) {
            /* shutdown? */
            return 0;
        } else if (rc == -1) {
            if (errno == EINTR) continue;
            perror("netlink recv");
            return -1;
        }
        switch (nlcn_msg.proc_ev.what) {
            case PROC_EVENT_NONE:
                printf("set mcast listen ok\n");
                break;
            case PROC_EVENT_FORK:
                printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n",
                        nlcn_msg.proc_ev.event_data.fork.parent_pid,
                        nlcn_msg.proc_ev.event_data.fork.parent_tgid,
                        nlcn_msg.proc_ev.event_data.fork.child_pid,
                        nlcn_msg.proc_ev.event_data.fork.child_tgid);
                break;
            case PROC_EVENT_EXEC:
                printf("exec: tid=%d pid=%d\n",
                        nlcn_msg.proc_ev.event_data.exec.process_pid,
                        nlcn_msg.proc_ev.event_data.exec.process_tgid);
                break;
            case PROC_EVENT_UID:
                printf("uid change: tid=%d pid=%d from %d to %d\n",
                        nlcn_msg.proc_ev.event_data.id.process_pid,
                        nlcn_msg.proc_ev.event_data.id.process_tgid,
                        nlcn_msg.proc_ev.event_data.id.r.ruid,
                        nlcn_msg.proc_ev.event_data.id.e.euid);
                break;
            case PROC_EVENT_GID:
                printf("gid change: tid=%d pid=%d from %d to %d\n",
                        nlcn_msg.proc_ev.event_data.id.process_pid,
                        nlcn_msg.proc_ev.event_data.id.process_tgid,
                        nlcn_msg.proc_ev.event_data.id.r.rgid,
                        nlcn_msg.proc_ev.event_data.id.e.egid);
                break;
            case PROC_EVENT_EXIT:
                printf("exit: tid=%d pid=%d exit_code=%d\n",
                        nlcn_msg.proc_ev.event_data.exit.process_pid,
                        nlcn_msg.proc_ev.event_data.exit.process_tgid,
                        nlcn_msg.proc_ev.event_data.exit.exit_code);
                break;
            default:
                printf("unhandled proc event\n");
                break;
        }
    }

    return 0;
}

static void on_sigint(int unused)
{
    need_exit = true;
}

int main(int argc, const char *argv[])
{
    int nl_sock;
    int rc = EXIT_SUCCESS;

    signal(SIGINT, &on_sigint);
    siginterrupt(SIGINT, true);

    nl_sock = nl_connect();
    if (nl_sock == -1)
        exit(EXIT_FAILURE);

    rc = set_proc_ev_listen(nl_sock, true);
    if (rc == -1) {
        rc = EXIT_FAILURE;
        goto out;
    }

    rc = handle_proc_ev(nl_sock);
    if (rc == -1) {
        rc = EXIT_FAILURE;
        goto out;
    }

    set_proc_ev_listen(nl_sock, false);

out:
    close(nl_sock);
    exit(rc);
}

(gcc -o proc proc.c)

И некоторая информация о netlink:

выдержка: http://www.linuxjournal.com/article/7356

Netlink является асинхронным, поскольку, как и любой другой API сокетов, он обеспечивает очередь сокетов, чтобы сгладить пакет сообщений. Системный вызов для отправки сообщения netlink ставит в очередь сообщение в очередь Netlink приемника, а затем вызывает обработчик приема приемника. Приемник в контексте обработчика приема может решить, нужно ли немедленно обработать сообщение или оставить сообщение в очереди и обработать его позже в другом контексте. В отличие от netlink, системные вызовы требуют синхронной обработки. Поэтому, если мы используем системный вызов для передачи сообщения из пользовательского пространства в ядро, степень детализации планирования ядра может быть затронута, если время обработки этого сообщения длинное.

Кроме того, это интересное объявление для nltrace, сделанное недавно, также может показаться вам интересным! http://lists.infradead.org/pipermail/libnl/2013-April/000993.html

Ответ 4

Используйте системную команду "pidof" из библиотеки procps. Очень простой и простой в использовании. Если он что-то возвращает, процесс запускается или наоборот.