[LINUX] signal Connaître l'expéditeur

introduction

Le signal est souvent utilisé pour la communication asynchrone entre les processus, J'ai besoin de connaître l'expéditeur et j'ai essayé de savoir comment le faire, je vais donc le laisser sous forme de mémorandum.

L'environnement que j'ai essayé est le suivant.

$ lsb_release -d
Description:	Ubuntu 18.04.4 LTS
$ uname -r
5.3.0-61-generic
$ trace-cmd --version | grep version
trace-cmd version 2.6.1
$ stap --version | head -1
Systemtap translator/driver (version 4.3/0.170, commit release-4.3-0-gc9c23c987d81)

Méthode 1. Connaître par sigaction (2)

Il s'agit d'une approche pour obtenir les informations de source dans le processus de réception du signal.

Vous pouvez modifier le processus de réception du signal, Et c'est une méthode qui peut être utilisée lorsque le signal qui peut être traité dans le processus fait l'objet d'une enquête. Il ne peut pas être utilisé pour les signaux qui ne peuvent pas être traités par le processus, tels que SIGKILL.

sigaction (2) peut être fait en spécifiant SA_SIGINFO dans sa_flags. Le gestionnaire de signaux pourra recevoir des informations détaillées sur le signal siginfo_t. Vous pouvez connaître le PID de l'expéditeur avec si_pid et l'ID utilisateur réel de l'expéditeur avec si_uid.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

static int g_sig;
static siginfo_t g_siginfo;

static void
my_sigaction (int sig, siginfo_t *info, void *ucontext)
{
  g_sig = sig;
  // memcpy(3) is a async-signal-safe function
  // according to man signal-safety(7)
  memcpy (&g_siginfo, info, sizeof(g_siginfo));
}

static char*
code2str (int code)
{
  switch (code)
    {
    case SI_USER: return "SI_USER";
    case SI_KERNEL: return "SI_KERNEL";
    case SI_QUEUE: return "SI_QUEUE";
    case SI_TIMER: return "SI_TIMER";
    case SI_MESGQ: return "SI_MESGQ";
    case SI_ASYNCIO: return "SI_ASYNCIO";
    case SI_SIGIO: return "SI_SIGIO";
    case SI_TKILL: return "SI_TKILL";
    default: return "unknown";
    }
}

int
main (void)
{
  struct sigaction act;
  act.sa_flags = SA_SIGINFO;
  act.sa_sigaction = my_sigaction;
  int ret = sigaction(SIGTERM, &act, NULL);
  if (ret < 0)
    {
      perror ("sigaction");
      exit (EXIT_FAILURE);
    }
  printf ("pid: %d\n", getpid());

  sleep (10000);

  fprintf (stderr, "sig: %d, si_pid: %d, si_uid: %d, si_code: %s\n",
           g_sig,
           g_siginfo.si_pid,
           g_siginfo.si_uid,
           code2str(g_siginfo.si_code));

  return 0;
}

Méthode 2. Savoir avec trace-cmd (= ftrace)

Puisque le noyau sait qui a envoyé le signal à qui, c'est une approche pour le savoir en traçant les événements du noyau. Utilisez ftrace pour le suivi des événements du noyau.

Vous pouvez identifier la source en traçant l'événement signal_generate avec ftrace. Vous pouvez utiliser ftrace directement à partir de debugfs, mais ici nous allons vous montrer comment tracer en utilisant trace-cmd.

[Commencer le traçage]
$ sudo trace-cmd start -e signal_generate -f 'sig==9 && pid == 6985'

[Fin de trace]
$ sudo trace-cmd stop

[Vérifiez le résultat de la trace]
$ sudo trace-cmd show
# tracer: nop
#
# entries-in-buffer/entries-written: 1/1   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
     test_sender-6993  [001] d...  1660.447368: signal_generate: sig=9 errno=0 code=0 comm=test_receiver pid=6985 grp=1 res=0

Vous pouvez voir que c'est le processus avec le PID test_sender 6993 qui a envoyé sig = 9 au processus avec pid = 6985.

Puisque l'événement signal_generate se produit ici en grand nombre, il est préférable d'utiliser l'option -f comme décrit ci-dessus et de la filtrer de manière appropriée. Dans ce qui précède, nous filtrons par le numéro du signal et le pid qui reçoit le signal.

Vous pouvez vérifier les noms de champs qui peuvent être utilisés dans la fonction de filtrage avec la commande trace-cmd list.

$ trace-cmd list -F -e signal_generate
system: signal
name: signal_generate
ID: 194
format:
	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
	field:int common_pid;	offset:4;	size:4;	signed:1;

	field:int sig;	offset:8;	size:4;	signed:1;
	field:int errno;	offset:12;	size:4;	signed:1;
	field:int code;	offset:16;	size:4;	signed:1;
	field:char comm[16];	offset:20;	size:16;	signed:1;
	field:pid_t pid;	offset:36;	size:4;	signed:1;
	field:int group;	offset:40;	size:4;	signed:1;
	field:int result;	offset:44;	size:4;	signed:1;

Méthode 3. Savoir avec le robinet système

Similaire à la méthode 2, il s'agit d'une approche pour vérifier en traçant les événements du noyau. Utilisez systemtap pour le suivi des événements du noyau.

Vous pouvez surveiller avec une doublure comme indiqué ci-dessous.

$ stap -e 'probe signal.send { if (sig==9 && sig_pid==8722) printf("%s: %s(%d) -> %s(%d)\n", sig_name, execname(), pid(), pid_name, sig_pid) }'
SIGKILL: test_sender(9191) -> test_receiver(8722)

Si vous créez un script puis prenez des arguments, ce sera comme suit. (Il semble que qiita ne dispose pas du mode tap système, donc j'utilise le mode langage C)

signal_sender.stp


#!/usr/bin/env stap
probe signal.send
{
  if (sig==strtol(@1,10) && sig_pid==strtol(@2,10))
    printf("%s: %s(%d) -> %s(%d)\n", sig_name, execname(), pid(), pid_name, sig_pid)
}

systemtap peut être intelligent comme ça sur un PC, L'inconvénient de l'incorporation est que la procédure est un peu compliquée car l'environnement de compilation et l'environnement d'exécution sont séparés.

Résumé

Je pense qu'il y en a d'autres, mais j'ai présenté trois options. Une des façons dont j'ai recherché était d'utiliser l'audit.log de SELinux (voir ici si cela vous intéresse). Personnellement, je pense que ftrace (trace-cmd) a un bon équilibre entre la facilité d'utilisation et les obstacles environnementaux. Je suis un peu épuisé, mais je pense que je peux le faire avec eBPF, donc j'aimerais bientôt ajouter la version eBPF.

référence

Man page of SIGACTION trace-cmd(1) - Linux man page stap(1): systemtap script translator/driver - Linux man page Comment utiliser la commande trace-cmd --Qiita Comment suivre la source de SIGKILL vers un portail client Process-Red Hat (https://access.redhat.com/en/solutions/385753) takeoverjp/test_signal: signal simple test program

Recommended Posts

signal Connaître l'expéditeur
Je ne connais pas l'erreur de valeur
Je ne connaissais pas les bases de Python