Как обнаружить запуск программ в Linux?

Я написал простой демон. Этот демон должен реагировать, когда я запускаю любую программу. Как это сделать? В большом цикле демона:

while(1)
{
   /* function which catches new programm running */
}

Какие функции вызывать в Linux, когда я запускаю новую программу (создаю новый процесс)?


person nub    schedule 20.05.2011    source источник
comment
Я думаю, нам понадобится немного больше информации   -  person Adam Batkin    schedule 20.05.2011
comment
@Adam Batkin - я не согласен, вопрос достаточно сформулирован, постер хочет получить информацию о создании процесса   -  person Chris Stratton    schedule 20.05.2011
comment
Этот демон запускает firefox или firefox запускается пользователем извне, и вы хотите получать уведомления? Ваш вопрос может потребовать небольшого пояснения.   -  person Justin Dearing    schedule 20.05.2011
comment
@Chris: Это, безусловно, верная интерпретация. Не стесняйтесь редактировать вопрос, чтобы сделать его более понятным   -  person Adam Batkin    schedule 20.05.2011
comment
перепишите мой вопрос, извините за мой плохой английский   -  person nub    schedule 20.05.2011
comment
Что ж, первая идея не подходит — inotify() не работает с /proc...   -  person Chris Stratton    schedule 20.05.2011
comment
@nub Вы удалили информацию из вопроса? Это действительно бесполезно!   -  person Robin Green    schedule 20.05.2011
comment
Как команда «top» находит pid новой программы? мне нужна аналогичная функция   -  person nub    schedule 20.05.2011
comment
Я считаю, что «top» и «ps» периодически и активно читают записи в / proc - для каждого процесса есть пронумерованный псевдокаталог. Вы, конечно, можете это сделать, но я не понял, как вызвать что-то, что будет блокироваться до тех пор, пока не произойдет изменение, после чего вы будете разбужены и уведомлены.   -  person Chris Stratton    schedule 20.05.2011
comment
демон аудита может регистрировать запущенные программы, отслеживая системный вызов execve. Может быть, вы можете вдохновиться от него.   -  person Cristian Ciupitu    schedule 21.05.2011
comment
@Crisitan Ciupitu - я думаю, что это стоило бы опубликовать в качестве ответа, если вы готовы немного написать об этом.   -  person Chris Stratton    schedule 21.05.2011


Ответы (8)


Я не знаю, существует ли лучший способ, но вы можете периодически сканировать файловую систему /proc.

Например, /proc/<pid>/exe — это символическая ссылка на исполняемый файл процесса.

В моих системах (Ubuntu/RedHat) /proc/loadavg содержит количество запущенных процессов (число после косой черты), а также pid самого последнего запущенного процесса. Если ваш демон опрашивает файл, любое изменение любого из двух чисел сообщит ему, когда ему нужно повторно /proc просканировать в поисках новых процессов.

Это ни в коем случае не пуленепробиваемый, но самый подходящий механизм, который я могу придумать.

person NPE    schedule 20.05.2011
comment
Спасибо! /proc/loadavg очень хорошо. Но как добавить информацию в /proc/loadavg? - person nub; 20.05.2011
comment
@nub: я предлагаю /proc/loadavg в качестве механизма уведомления (который должен быть опрошен). Он не скажет вам ничего, кроме того, что что-то изменилось. Чтобы выяснить, что изменилось, вам нужно отсканировать /proc/[0-9]*. - person NPE; 20.05.2011
comment
Да, но как попала информация о новом pid? Потому что сначала эта информация попадает(как?), а потом запихивается в /proc/loadavg. - person nub; 20.05.2011
comment
/proc/loadavg — это своего рода поддельный файл, экспортируемый ядром. Когда вы пытаетесь его прочитать, вы получаете информацию прямо из ядра. - person Chris Stratton; 20.05.2011
comment
Вы могли пропустить запуск и завершение десятков процессов между двумя сканированиями и то, что ничего не произошло, пока процессы запускались и завершались. - person LtWorf; 03.12.2012
comment
Для правильного подхода к событиям ядра см. Мой ответ ниже. - person Arnaud Meuret; 08.07.2020

Для Linux в ядре есть интерфейс. Исследуя эту проблему, я столкнулся с людьми, использующими конфигурацию ядра CONFIG_CONNECTOR и CONFIG_PROC_EVENTS для получения событий о смерти процесса.

Еще немного Google, и я нашел это:

http://netsplit.com/2011/02/09/the-proc-connector-and-socket-filters/

Коннектор Proc и фильтры сокетов

Коннектор proc — это одна из тех интересных функций ядра, с которыми большинство людей редко сталкивается, и еще реже находят документацию по ней. Аналогично фильтру сокетов. Это позор, потому что они оба действительно очень полезные интерфейсы, которые могли бы служить различным целям, если бы они были лучше задокументированы.

Коннектор proc позволяет получать уведомления о событиях процесса, таких как вызовы fork и exec, а также об изменениях uid, gid или sid процесса (идентификатор сеанса). Они предоставляются через интерфейс на основе сокетов путем чтения экземпляров struct proc_event, определенных в заголовке ядра....

Заголовок интереса:

#include <linux/cn_proc.h>

Я нашел пример кода здесь:

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

/* This file is licensed under the GPL v2 (http://www.gnu.org/licenses/gpl2.txt) (some parts was originally borrowed from proc events example)

pmon.c

code highlighted with GNU source-highlight 3.1
*/

#define _XOPEN_SOURCE 700
#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);
}

Я обнаружил, что этот код должен запускаться от имени пользователя root, чтобы получать уведомления.

person David Crookes    schedule 24.11.2011
comment
Великолепный - помогает создать решение для superuser.com/questions/354150/ ;-) - person FrankH.; 17.10.2013
comment
Это красиво, я уже начал думать, что мне придется погрузиться в код ядра... - person notbad.jpeg; 19.01.2016
comment
Обратите внимание, что вы можете использовать pid созданных процессов для чтения дополнительной информации о процессах из каталога /proc/‹pid›, если это необходимо. Я использовал это для извлечения информации из командной строки, чтобы также отображать имя процесса при создании. - person h0r53; 20.06.2018

Мне было интересно попытаться выяснить, как это сделать без опроса. inotify() не работает с /proc, так что эта идея отпадает.

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

#include <stdio.h>
#include <sys/inotify.h>
#include <assert.h>
int main(int argc, char **argv) {
    char buf[256];
    struct inotify_event *event;
    int fd, wd;
    fd=inotify_init();
    assert(fd > -1);
    assert((wd=inotify_add_watch(fd, "/lib/ld-linux.so.2", IN_OPEN)) > 0);
    printf("Watching for events, wd is %x\n", wd);
    while (read(fd, buf, sizeof(buf))) {
      event = (void *) buf;
      printf("watch %d mask %x name(len %d)=\"%s\"\n",
         event->wd, event->mask, event->len, event->name);
    }
    inotify_rm_watch(fd, wd);
    return 0;
}

События, которые это распечатывает, не содержат никакой интересной информации - pid запускающего процесса, похоже, не предоставляется inotify. Однако его можно использовать для пробуждения и повторного сканирования /proc.

Также имейте в виду, что недолговечные программы могут снова исчезнуть до того, как эта штука проснется и завершит сканирование /proc - по-видимому, вы узнаете, что они существовали, но не сможете узнать, что они из себя представляли. И, конечно же, любой может просто продолжать открывать и закрывать fd для динамического компоновщика, чтобы утопить вас в шуме.

person Chris Stratton    schedule 20.05.2011

Используйте forkstat, это самый полный клиент для событий proc:

sudo forkstat -e exec,comm,core

Упакован в Ubuntu, Debian и AUR.


До этого был cn_proc:

 bzr branch lp:~kees/+junk/cn_proc

Makefile нуждается в небольшом изменении (LDLIBS вместо LDFLAGS).

cn_proc и exec-notify.c (который опубликовал Арно) имеют общего предка; cn_proc обрабатывает еще несколько событий и имеет более чистый вывод, но не устойчив к быстрому завершению процессов.


О, нашел еще один форк exec-notify, extrace. Этот отступает дочерним процессам ниже их родителя (используя эвристику pid_depth).

person Tobu    schedule 18.12.2014

Взгляните на эту небольшую программу Себастьяна Крамера, она делает именно то, что вы просите в ресурсе эффективный способ и довольно простой код.

Для этого требуется, чтобы в вашем ядре был включен CONFIG_PROC_EVENTS, чего нет, например, в последнем образе Amazon Linux (2012.09).

ОБНОВЛЕНИЕ: после запроса к Amazon ядра Amazon Linux Image теперь поддерживают PROC_EVENTS

person Arnaud Meuret    schedule 12.09.2012
comment
эта маленькая ссылка на программу не работает. Обновить это?: gist.github.com/iNarcissuss/4809c725eb04b070f439d9c6df28cdd7 - person voxoid; 03.06.2020

Ключевое слово для поисковой машины по вашему выбору — «коннектор событий процесса».

Я нашел два инструмента, которые их используют: exec-notify и cn_proc.

Последний мне нравится больше, но оба отлично справляются со своей задачей.

person Ikem Krueger    schedule 04.01.2015

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

Сканирование может быть выполнено с помощью системного вызова ядра или путем чтения сведений о ядре, объявленных пользовательским пространством (как в файловой системе /proc). Обратите внимание, что сканирование не является гарантией того, что вы найдете какую-либо конкретную программу, так как если программа успевает запускаться и завершаться между циклами сканирования, она вообще не будет обнаружена.

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

person Edwin Buck    schedule 20.05.2011

CONFIG_FTRACE и с CONFIG_KPROBES по brendangregg/perf-tools

git clone https://github.com/brendangregg/perf-tools.git
cd perf-tools
git checkout 98d42a2a1493d2d1c651a5c396e015d4f082eb20
sudo ./execsnoop

В другой оболочке:

while true; do sleep 1; date; done

Первая оболочка показывает данные формата:

Tracing exec()s. Ctrl-C to end.                                                        
Instrumenting sys_execve                                                               
   PID   PPID ARGS 
 20109   4336 date                                                                                       
 20110   4336 sleep 1                                                                                    
 20111   4336 date                                                                                                                                                                                                 
 20112   4336 sleep 1                                                                                    
 20113   4336 date                                                                                       
 20114   4336 sleep 1                                                                                    
 20115   4336 date                                                                                       
 20116   4336 sleep 1
person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 06.05.2018