2010-04-18 6 views
55

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

char *buffer; 
char *p; 
char a; 
int pagesize = 4096; 

mprotect(buffer,pagesize,PROT_NONE) 

Это защищает PAGESIZE байт памяти, начиная с буфера против любого чтения или записи.

Во-вторых, я пытаюсь прочитать память:

p = buffer; 
a = *p 

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

mprotect(buffer,pagesize,PROT_READ); 

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

Вот the code:

#include <signal.h> 
#include <stdio.h> 
#include <malloc.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <sys/mman.h> 

#define handle_error(msg) \ 
    do { perror(msg); exit(EXIT_FAILURE); } while (0) 

char *buffer; 
int flag=0; 

static void handler(int sig, siginfo_t *si, void *unused) 
{ 
    printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr); 
    printf("Implements the handler only\n"); 
    flag=1; 
    //exit(EXIT_FAILURE); 
} 

int main(int argc, char *argv[]) 
{ 
    char *p; char a; 
    int pagesize; 
    struct sigaction sa; 

    sa.sa_flags = SA_SIGINFO; 
    sigemptyset(&sa.sa_mask); 
    sa.sa_sigaction = handler; 
    if (sigaction(SIGSEGV, &sa, NULL) == -1) 
     handle_error("sigaction"); 

    pagesize=4096; 

    /* Allocate a buffer aligned on a page boundary; 
     initial protection is PROT_READ | PROT_WRITE */ 

    buffer = memalign(pagesize, 4 * pagesize); 
    if (buffer == NULL) 
     handle_error("memalign"); 

    printf("Start of region:  0x%lx\n", (long) buffer); 
    printf("Start of region:  0x%lx\n", (long) buffer+pagesize); 
    printf("Start of region:  0x%lx\n", (long) buffer+2*pagesize); 
    printf("Start of region:  0x%lx\n", (long) buffer+3*pagesize); 
    //if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) 
    if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) 
     handle_error("mprotect"); 

    //for (p = buffer ; ;) 
    if(flag==0) 
    { 
     p = buffer+pagesize/2; 
     printf("It comes here before reading memory\n"); 
     a = *p; //trying to read the memory 
     printf("It comes here after reading memory\n"); 
    } 
    else 
    { 
     if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1) 
     handle_error("mprotect"); 
     a = *p; 
     printf("Now i can read the memory\n"); 

    } 
/* for (p = buffer;p<=buffer+4*pagesize ;p++) 
    { 
     //a = *(p); 
     *(p) = 'a'; 
     printf("Writing at address %p\n",p); 

    }*/ 

    printf("Loop completed\n");  /* Should never happen */ 
    exit(EXIT_SUCCESS); 
} 

Проблема заключается в том, что только работает обработчик сигнала, и я не могу вернуться к основной функции после ловли сигнала.

+2

спасибо за редактирование..Я ценю это. Мне нужно потратить некоторое время, чтобы научиться редактировать свои вопросы. – Adi

+0

при компиляции всегда включайте все предупреждения, а затем исправляйте эти предупреждения. (для 'gcc', при минимальном использовании:' -Wall -Wextra -pedantic' Я также использую: '-Wconversion -std = gnu99') Компилятор скажет вам: 1) параметр' argc' unused 2) parameter ' argv' unused (предложите использовать main() подпись: 'int main (void)' 3) локальная переменная 'p', используемая в блоке кода' else', без инициализации. 4) параметр 'unused' unused, предложите: добавить оператор:' (void) unused; 'как первая строка в этой функции. 5) локальная переменная 'a' установлена, но не используется. – user3629249

+0

НИКОГДА не используйте 'printf()' в обработчике сигналов! Функция 'write()' была бы удобна в использовании, но лучше не делать никаких операций ввода-вывода в обработчике сигнала, просто установите флаг и пусть основная строка кода будет проверять этот флаг – user3629249

ответ

57

Когда ваш обработчик сигнала возвращается (при условии, что он не вызывает exit или longjmp или что-то, что мешает ему фактически возвращаться), код будет продолжаться в точке, где произошел сигнал, повторно выполнив ту же инструкцию. Поскольку на данный момент защита памяти не была изменена, она просто снова выдает сигнал, и вы вернетесь в свой обработчик сигнала в бесконечном цикле.

Чтобы заставить его работать, вы должны вызвать mprotect в обработчике сигналов. К сожалению, как отмечает Стивен Шанскер, mprotect не является безопасным для асинхронизации, поэтому вы не можете безопасно называть его обработчиком сигнала. Итак, что касается POSIX, вы ввернуты.

К счастью для большинства реализаций (все современные версии UNIX и Linux, насколько мне известно), mprotect является системным вызовом, поэтому safe to call from within a signal handler, поэтому вы можете делать большую часть того, что хотите.Проблема в том, что если вы хотите изменить защиту после чтения, вам нужно будет сделать это в основной программе после чтения.

Другая возможность состоит в том, чтобы что-то сделать с третьим аргументом обработчика сигнала, который указывает на ОС и сводную структуру, содержащую информацию о том, где произошел сигнал. В Linux это структура ucontext, которая содержит информацию о адресе $ PC и другом регистровом содержимом, где указан сигнал. Если вы измените это значение, вы измените место, где обработчик сигнала вернется, поэтому вы можете изменить $ PC, чтобы сразу после инструкции по сбою, чтобы он не перезапустился после возврата обработчика. Это очень сложно сделать правильно (и не переносить).

редактировать

ucontext структура определена в <ucontext.h>. В пределах ucontext поле uc_mcontext содержит контекст машины и в пределах , что, массив gregs содержит общий контекст регистра. Поэтому в вашем обработчике сигналов:

ucontext *u = (ucontext *)unused; 
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP]; 

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

Что касается переносимости вызова mprotect в обработчике сигналов, любая система, которая следует либо спецификации SVID, либо спецификации BSD4 должна быть безопасной - они позволяют вызывать любой системный вызов (что-либо в разделе 2 руководства) в обработчике сигналов.

+0

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

+0

hi chris, Вы дали мне полезную информацию. Спасибо за это. Можете ли вы рассказать мне, как я могу прочитать информацию в структуре ucontext (третий аргумент и изменить $ PC). Мне любопытно узнать об этом. – Adi

+0

@ Бен Фойгт, я не понял, что вы говорите, попросите вас быть немного более сложным. – Adi

20

Вы попали в ловушку, которую все люди делают, когда они сначала пытаются обрабатывать сигналы. Ловушка? Думая, что вы действительно можете сделать что-нибудь полезно с обработчиками сигналов. Из обработчика сигнала вам разрешено вызывать только асинхронные и повторно-безопасные вызовы библиотеки.

См. this CERT advisory о том, почему и список функций POSIX, которые являются безопасными.

Обратите внимание, что printf(), который вы уже вызываете, отсутствует в этом списке.

Не является mprotect. Вы не можете называть это из обработчика сигнала. Это может работы, но я могу пообещать, что вы столкнетесь с проблемами в будущем. Будьте очень осторожны с обработчиками сигналов, они сложны, чтобы получить право!

EDIT

Поскольку я быть портативность Douchebag на уже данный момент, я укажу, что вы also shouldn't write to shared (i.e. global) variables без принятия надлежащих мер предосторожности.

+0

Привет, steven, Если я не могу сделать что-либо полезное внутри обработчика сигнала, я буду в порядке, если я смогу обновить некоторые счетчики внутри него и вернуться к основному и, как правило, запустить свой код, возможно ли это? – Adi

+0

, ссылаясь на консультацию CERT, «они могут вызывать другие функции при условии, что все реализации, которым переносится код, гарантируют, что эти функции асинхронны». На linux, который включает в себя намного больше функций. –

+0

Конечно, но вам нужно просто осознавать эту проблему! Я не могу назвать свою голову, какие функции являются и не являются безопасными для сигнала, и я сомневаюсь, что многие могут! –

8

Вы можете восстановить SIGSEGV на linux. Также вы можете восстановить из-за ошибок сегментации в Windows (вы увидите структурированное исключение вместо сигнала). Но the POSIX standard doesn't guarantee recovery, поэтому ваш код будет очень не переносным.

Посмотрите на libsigsegv.

4

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

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

На самом деле, я знаю очень мало применений обработчика SIGSEGV:

  • использовать асинхронный сигнал безопасной библиотеку трассировки войти трассировку, а затем умирает.
  • в виртуальной машине, такой как JVM или CLR: проверьте, произошел ли SIGSEGV в JIT-компилированном коде. Если нет, умрете; если это так, то бросайте исключение, специфичное для языка (не исключение C++), которое работает, потому что компилятор JIT знал, что может произойти ловушка и сгенерировать соответствующие данные для размотки кадра.
  • clone() и exec() отладчик (do не use fork() - который вызывает обратные вызовы, зарегистрированные pthread_atfork()).

Наконец, обратите внимание, что любое действие, которое запускает SIGSEGV, вероятно, является UB, поскольку это обращение к недопустимой памяти. Однако это не так, если бы сигнал был, скажем, SIGFPE.