29 Mart 2018 Perşembe

sigaction metodu

Giriş
Şu satırı dahil ederiz.
#include <signal.h>
Sinyalleri kullanırken yapılan klasik bir hata şu. Aynı metoda birden fazla sinyal bağlıyoruz.

signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGQUIT, signal_handler);

ve signal_handler metodunun bir sinyal bitmeden bir kere daha çağrılmayacağını düşünüyoruz. Halbuki böyle bir kural yok. signal_handler bir sinyali işlememiz bitmeden bir kere daha çağrılabilir.

Dolayısıyla böyle kodlayacaksak signal_handler metodunun reentrant olması gerekir.
1. sigaction metodu
sigaction metodunun imzası şöyle.
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
Metodun ilk parametresi bağlanılmak istenilen sinyal, ikinci parametresi ise bir struct. Şöyle kullanırız.
struct sigaction act;
...
sigaction(SIGUSR1, &act, NULL);
Şöyle yaparız.
struct sigaction act;
...
sigaction(SIGFPE, &sa, NULL);
Posix sistemlerde signal() çağrısı yerine sigaction() çağrısının kullanılması öneriliyor.

Bu metodun Windows dünyasında direkt karşılığı yok. Ancak burada (Unix to Windows Porting Dictionary) Windows'ta nasıl yapılabileceği gösterilmiş.

2. struct sigaction
Bu metoda geçilen struct sigaction ise aşağıdaki yapıya sahip. Hem metodun hem de struct'ın aynı isme sahip olması bence kötü olmuş.
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void * );
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
Yukarıdaki struct içinde hem sa_handler hem de sa_action aslında callback olarak kullanılabiliyorlar.

Yapıyı kullamadan önce temizlemek iyi bir fikir. Şöyle yaparız.
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
1. sa_mask alanının kullanımı
Bu alan sa_flags ile kurulan sinyal çalışırken, bloke edilmesi (mask off) istenilen sinyalleri belirtir.
Çoğu kodda memset yaptıktan sonra sigemptyset ile sa_mask alanı da sıfırlanıyor. Yapmak lazım mı emin değilim. Şöyle yaparız.
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset (&sa.sa_mask);
Secure Coding Prensiplerinde "Do not access shared objects in signal handlers" deniyor. İstisnai kural olarak volatile sig_atomic_t veri tipi hariç tutulmuş. volatile sig_atomic_t e_flag = 0; şeklinde bir değişken tanımlanabilir ve kullanılabilir.

2. sa_flags alanı
Bu alan genellike 0 verilir. Şöyle yaparız.
struct sigaction newsigfunc;
newsigfunc.sa_handler = catch_alarm;
sigemptyset(&newsigfunc.sa_mask);
newsigfunc.sa_flags = 0;

sigaction(SIGALRM, &newsigfunc, NULL);
SA_NODEFER bayrağı
Açıklaması şöyle.
SA_NODEFER: Do not prevent the signal from being received from within its own signal handler. SA_NOMASK is an obsolete, non-standard synonym for this flag.
Normalde signal handler bitmeden tekrar çağrılmaz. Yani reentrant değildir. Elimizde şöyle bir kod olsun. Signal handler döngü içinde çalıştığı için ve reentrant olmadığı için Ctrl+C'ye bir kere basınca handler çağrılır. İkinci basmada ise çağrılmaz.
#include<stdio.h>
#include<signal.h>

void handler(int signo)
{
  printf("Into handler\n");
  while(1);
}
int main()
{
  struct sigaction act;
  act.sa_handler = handler;
  act.sa_flags = 0;
  sigemptyset(& act.sa_mask);
  sigaction(SIGINT, &act, NULL);
  while(1);
  return 0;
}
SA_ONSTACK bayrağı
Her sinyalin farklı bir stack ile çağrılmasını sağlar.
struct sigaction act;
act.sa_handler = signal_andler;
act.sa_mask = 0;
act.sa_flags = SA_ONSTACK;
sigaction(SIGUSR1, &act, 0);
SA_RESETHAND bayrağı
Sinyalin tek seferlik (one-shot) olmasını sağlar. signal() kullanıyor olsaydık sinyal tek seferlik olduğu için tekrar kurmak için şöyle yapardık
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


/* This flag controls termination of the main loop. */
volatile sig_atomic_t keep_going = 1;

/* The signal handler just clears the flag and re-enables itself. */
void
catch_alarm (int sig)
{
    keep_going = 0;
    signal (sig, catch_alarm);   //  <----- ???
}

void
do_stuff (void)
{
    puts ("Doing stuff while waiting for alarm....");
}

int
main (void)
{
    /* Establish a handler for SIGALRM signals. */
    signal (SIGALRM, catch_alarm);

    /* Set an alarm to go off in a little while. */
    alarm (2);

    /* Check the flag once in a while to see when to quit. */
    while (keep_going)
        do_stuff ();

    return EXIT_SUCCESS;
}
signal()'in açıklaması şöyle.
In the original UNIX systems, when a handler that was established using signal() was invoked by the delivery of a signal, the disposition of the signal would be reset to SIG_DFL, and the system did not block delivery of further instances of the signal. This is equivalent to calling sigaction(2) with the following flags:
sa.sa_flags = SA_RESETHAND | SA_NODEFER;

SA_SIGINFO bayrağı
sa_sigaction alanına atanan signal handler'ın 3 parametre ile çağrılmasını sağlar.

3. sa_sigaction alanının kullanımı
Şöyle yaparız. Örnekte sigaction() çağrısının son parametresi NULL. Yani bir önceki old action handler'ı tekrar kullanmak istemiyoruz.
static void handler (int sig, siginfo_t *siginfo, void *pip)
{
    xxxxxxx
}

int main() {
  struct sigaction act;
  memset (&act, '\0', sizeof(act));
  act.sa_sigaction = &handler;
  act.sa_flags = SA_SIGINFO;
  sigaction(SIGINT, &act, NULL);
  ...

  return 0;
}
3.1 sa_handler için kullanılan handler metodun açıklaması
İmzası şöyle
void sig_handler(int signo){
  ...
}
Bu metod içinde sadece şu metodlar kullanılabilir.
Async-signal-safe functions
   A signal handler function must be very careful, since processing  else
   where  may  be  interrupted at some arbitrary point in the execution of
   the program.  POSIX has the concept of "safe function".   If  a  signal
   interrupts  the  execution  of  an  unsafe function, and handler either
   calls an unsafe function [...],
   then the behavior of the program is undefined.
fprintf () metodlardan değildir.

3.2 sa_sigaction için kullanılan handler metodun açıklaması
sig parametresi
Sinyalin numarasını verir.

siginfo_t parametresi
Sinyalin kaynağı hakkında bilgi içerir.

1. si_code alanı
sinyalin kullanıcı veya işletim sistemi tarafından gönderilip gönderilmediğini belirtir. Bu alan SI_USER ise kullanıcı tarafından gönderilmiştir.

2.  si_pid alanı
Örnek
static void multi_handler(int sig, siginfo_t *siginfo, void *context) {
    // get pid of sender,
    pid_t sender_pid = siginfo->si_pid;

    if(sig == SIGINT) {
        int_count++;
        printf("INT(%d), from [%d]\n", int_count, (int)sender_pid);
        return;
    } else if(sig == SIGQUIT) {
        printf("Quit, bye, from [%d]\n", (int)sender_pid);
        exit(0);
    }

    return;
}
context parametresi
Tamamen platforma mahsustur. Linux GNU'da şöyle kullanılabilir.
#include<stdio.h>
#define __USE_GNU
#include<signal.h>
#include<ucontext.h>

void myhandle(int mysignal, siginfo_t *si, void* arg)
{    
 ucontext_t *context = (ucontext_t *)arg;
 printf("Address where crash happened %x \n",context->uc_mcontext.gregs[REG_RIP]);
 context->uc_mcontext.gregs[REG_RIP] = context->uc_mcontext.gregs[REG_RIP] + 0x04;

}

int main(int argc, char *argv[])
{
  struct sigaction action;
  action.sa_sigaction = &myhandle;
  action.sa_flags = SA_SIGINFO;
  sigaction(11,&action,NULL);

  printf("Before segfault\n");

  int *a=NULL;
  int b;
  b =*a; // Here crash will hapen

  printf("I am still alive\n");

  return 0;
}
Çıktı olarak şunu alırız.
Before segfault
Signal is 11
Address from where crash happen is 40065b 
I am still alive
objdump ile uygulamaya bakarsak 40065b adresinde null pointer yüzünde crash olduğunu görebiliriz.
  printf("Before segfault\n");
  400645:   bf a8 07 40 00          mov    $0x4007a8,%edi
  40064a:   e8 21 fe ff ff          callq  400470 <puts@plt>

  int *a=NULL;
  40064f:   48 c7 45 f0 00 00 00    movq   $0x0,-0x10(%rbp)
  400656:   00 
  int b;
  b =*a; // Here crash will hapen
  400657:   48 8b 45 f0             mov    -0x10(%rbp),%rax
  40065b:   8b 00                   mov    (%rax),%eax
  40065d:   89 45 fc                mov    %eax,-0x4(%rbp)

  printf("I am still alive\n");
  400660:   bf b8 07 40 00          mov    $0x4007b8,%edi
  400665:   e8 06 fe ff ff          callq  400470 <puts@plt>
ucontext
ucontext'i kullanmak için şu satır dahil edilir.
#include <ucontext.h>
yapının içinde uc_stack ve uc_link, uc_mcontext gibi alanlar var.
uc_stack.ss_sp = ...;
uc_stack.ss_size = 1000;
uc_stack.ss_flags = 0;
uc_link = NULL;
getcontext() ve setcontext() ile bu yapı okunur ve atanabilir.
ucontext_t uctx_func1;

if (getcontext(&uctx_func1) == -1) {...error...}
register'a değer şöyle atanır.
ucontext_t cont;
getcontext (&cont);
cont.uc_mcontext.gregs[REG_ESP] = 0x355000;
...
setcontext(&cont);
makecontext() ve swapcontext() metodları da var ancak tam ne işe yarar bilmiyorum.

makecontext metodu
İmzası şöyle.
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
func için açıklaması şöyle
...the function func is called, and passed the series of integer (int) arguments that follow argc
Sebebi ise şöyle.
Note that, in order for the implementation of makecontext to actually call func, it has to know how to setup a stack frame for entry to a function with the right number and type of arguments. If all arguments are required to have the same type (int), it's possible to know that just from the number, argc. If they were allowed to have different types, the caller would somehow have to pass a specification of the types as an argument to makecontext; a simple count is not sufficient to know how to copy them from the va_list received by makecontext to the stack frame for entry to func.



Hiç yorum yok:

Yorum Gönder