programing

SIGSEGV를 잡기 위한 신호 핸들러 작성 방법

copysource 2022. 8. 27. 23:44
반응형

SIGSEGV를 잡기 위한 신호 핸들러 작성 방법

SIGSEGV를 잡기 위해 신호 핸들러를 쓰고 싶다.읽기 또는 쓰기를 위해 메모리 블록을 보호한다.

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

mprotect(buffer,pagesize,PROT_NONE)

이렇게 하면 버퍼에서 시작하는 메모리의 페이지 크기 바이트가 읽기 또는 쓰기로부터 보호됩니다.

둘째, 기억을 읽으려고 합니다.

p = buffer;
a = *p 

그러면 SIGSEGV가 생성되고 내 핸들러가 호출됩니다.아직까지는 좋아.문제는 핸들러가 호출되면 다음 작업을 수행하여 메모리의 액세스 쓰기를 변경하려는 것입니다.

mprotect(buffer,pagesize,PROT_READ);

내 코드의 정상적인 기능을 계속할 수 있습니다.기능을 종료하고 싶지 않습니다.나중에 같은 메모리에 쓸 때 다시 신호를 잡아 쓰기 권한을 수정한 후 해당 이벤트를 기록하려고 합니다.

코드는 다음과 같습니다.

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

문제는 신호 핸들러만 작동하고 신호를 잡은 후 본기능으로 돌아갈 수 없다는 것입니다.

신호 핸들러가 반환되었을 때(종료 또는 longjmp를 호출하지 않는다고 가정하거나 실제로 반환되지 않는 경우) 코드는 신호가 발생한 지점에서 계속되며 동일한 명령을 다시 실행합니다.이 시점부터 메모리 보호는 변경되지 않고 신호가 다시 느려지고 무한 루프 상태로 신호 핸들러로 돌아갑니다.

이 기능을 사용하려면 신호 핸들러에서 mprotect를 호출해야 합니다.불행히도 Steven Schansker가 지적했듯이 mprotect는 비동기적으로 안전하지 않기 때문에 신호 핸들러에서 안전하게 호출할 수 없습니다.POSIX에 관한 한 넌 망했어

다행히 대부분의 구현에서(내가 아는 바로는 모든 최신 UNIX 및 Linux 버전) mprotect는 시스템 호출이므로 신호 핸들러 내에서 호출해도 안전하므로 원하는 대부분의 작업을 수행할 수 있습니다.문제는 읽기 후 보호를 다시 변경하려면 읽기 후 기본 프로그램에서 변경해야 한다는 것입니다.

또 다른 방법으로는 신호가 발생한 위치에 대한 정보를 포함하는 OS 및 아치 고유의 구조를 가리키는 신호 핸들러를 세 번째 인수로 조작할 수 있습니다.Linux 에서는 ucontext 구조입니다.이 구조에는 $PC 주소 및 신호가 발생한 기타 레지스터 내용에 대한 머신 고유의 정보가 포함되어 있습니다.이를 수정하면 신호 핸들러가 복귀하는 위치가 변경되므로 $PC를 장애 명령 직후로 변경하여 핸들러가 복귀한 후 재실행하지 않도록 할 수 있습니다.이것은 올바르게 하기에는 매우 까다롭습니다(휴대용도 아닙니다).

편집하다

ucontext는 '구조'에 되어 있습니다.<ucontext.h>ucontextuc_mcontext머신의 컨텍스트가 포함되어 있으며, 그 에 어레이가 포함되어 있습니다.gregs에 일반 레지스터 컨텍스트를 나타냅니다.신호 핸들러에서는 다음과 같이 합니다.

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

예외가 발생한 PC가 표시됩니다.어떤 명령에서 오류가 발생했는지 확인하고 다른 작업을 수행할 수 있습니다.

신호 핸들러에서 mprotect를 호출하는 휴대성에 관한 한, SVID 사양 또는 BSD4 사양 중 하나를 따르는 시스템은 안전합니다.이러한 시스템에서는 신호 핸들러에서 시스템콜(매뉴얼 섹션2의 임의의 것)을 호출할 수 있습니다.

당신은 모든 사람들이 처음 신호를 다루려고 할 때 하는 함정에 빠졌어요.함정?신호 핸들러로 실제로 도움이 되는 것은 무엇이든 할 수 있다고 생각합니다.신호 핸들러에서는 비동기 및 재진입 세이프 라이브러리 콜만 호출할 수 있습니다.

이유와 안전한 POSIX 기능의 리스트에 대해서는, 이 CERT 권고를 참조해 주세요.

이미 호출하고 있는 printf()는 목록에 없습니다.

mprotect도 아니다.신호 처리기에서 호출할 수 없습니다. 될 수도 있지만 앞으로 문제가 생길 거라고 약속할 수 있어요.신호 핸들러를 정말 조심하세요, 제대로 맞추기가 어려워요!

편집

저는 지금 이미 휴대성이 떨어지는 사람이기 때문에 적절한 예방 조치를 취하지 않고 공유(글로벌) 변수에 글을 쓰면 안 된다는 점을 지적합니다.

리눅스의 SIGSEGV에서 복구할 수 있습니다.또, Windows 의 세그먼트화 장해로부터 회복할 수도 있습니다(신호 대신에 구조화된 예외가 표시됩니다).그러나 POSIX 규격에서는 복구를 보장하지 않기 때문에 코드는 휴대할 수 없습니다.

libsigsegv를 보세요.

동작은 정의되지 않으므로 신호 핸들러에서 복귀하지 마십시오.차라리 longjmp로 뛰어내리세요.

이것은 신호가 비동기 시그널 세이프 함수로 생성된 경우에만 가능합니다.그렇지 않으면 프로그램이 다른 비동기 시그널 언세이프 함수를 호출한 경우 동작은 정의되지 않습니다.따라서 신호 핸들러는 필요 직전에만 확립하고 가능한 한 빨리 확립을 해제해야 합니다.

실제로 SIGSEGV 핸들러의 사용법은 거의 알고 있지 않습니다.

  • async-disp-safe 백트레이스 라이브러리를 사용하여 백트레이스를 기록하고 다이(die)를 실행합니다.
  • JVM이나 CLR 등의 VM에서 SIGSEGV가 JIT 컴파일된 코드로 발생했는지 확인합니다.그렇지 않으면 die를 실행합니다.그렇다면 언어 고유의 예외(C++ 예외가 아님)를 설정합니다.이는 JIT 컴파일러가 트랩이 발생할 수 있음을 알고 적절한 프레임 언바인드 데이터를 생성했기 때문입니다.
  • clone() 및 exec() 디버거(pthread_atfork()에 의해 등록된 콜백을 호출하는 fork()는 사용하지 마십시오).

마지막으로 SIGSEGV를 트리거하는 작업은 UB일 수 있습니다.유효하지 않은 메모리에 액세스 하고 있기 때문입니다.단, 예를 들어 신호가 SIGFPE일 경우에는 해당되지 않습니다.

를 사용하여 컴파일 문제가 발생하였습니다.ucontext_t또는 구조ucontext(존재)/usr/include/sys/ucontext.h)

http://www.mail-archive.com/arch-general@archlinux.display/msg13853.displays

언급URL : https://stackoverflow.com/questions/2663456/how-to-write-a-signal-handler-to-catch-sigsegv

반응형