SIGSEGVを捕捉するライブラリの作成

はじめに

SIGSEGVを捕捉するライブラリを作成する.作成したライブラリはLD_PRELOADを用いて対象となる実行ファイルに,動的リンクさせる.
SIGSEGVの捕捉方法についてはSIGSEGVを捕捉するを参照.

プログラム

以下のようなSIGSEGVを発生させるプログラムを用意した.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* main.c */
#include<stdio.h>
#include<string.h>

int main(){

  printf("main() is called\n");

  char *ptr;
  *ptr = "A";
  return 0;
}

このプログラムに対して,LD_PRELOADを用いて,作成したライブラリを動的リンクさせることで,前回のように,シグナルの情報やコンテキストを表示させる.

ライブラリとなるプログラムは以下のようになる.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* my_libsignal.c */
#include <stdio.h>
#include <stdlib.h>
#define __USE_GNU
#include <signal.h>
#include <ucontext.h>
#include <string.h>
#include <sys/mman.h>

#define STACK_SIZE (4096*8)

static void signal_handler(int sig, siginfo_t* sig_info, void *sig_data)
{
  ucontext_t *ctx = sig_data;
  size_t *gregs = (size_t *)ctx->uc_mcontext.gregs;

  if (sig = SIGSEGV) {
     fprintf(stderr, "si_addr %p\n", sig_info->si_addr);
     fprintf(stderr, "[ucontext] %p\n", sig_data);
     fprintf(stderr, "[REG_RIP] %p\n", (void *)gregs[REG_RIP]);
     fflush(stderr);
//     exit(1);
  }
}

__attribute__((constructor))
void set_signal()
{
  stack_t ss;
  ss.ss_sp = malloc(STACK_SIZE);
  ss.ss_size = STACK_SIZE;
  ss.ss_flags = 0;
  if (sigaltstack(&ss, NULL)) {
    fprintf(stderr, "Failed sigaltstack\n");
    exit(1);
  }

  struct sigaction act;
  sigemptyset(&act.sa_mask);
  sigaddset(&act.sa_mask, SIGSEGV);
  act.sa_sigaction = signal_handler;
  act.sa_flags = SA_SIGINFO|SA_RESTART|SA_ONSTACK;
  if ( sigaction( SIGSEGV, &act, NULL ) == -1 ) {
    /* error */
    fprintf(stderr, "Failed to set my signal handler.\n" );
    exit(1);
  }

  printf("set_signal() is called\n");

}

main.cのプログラムの実行を開始する前に,独自のハンドラを登録する必要がある.そのため今回は,GCC拡張である__attribute__((constructor))を用いた.他の手法としては,__libc_start_main関数をライブラリ内でラップする手法がある.違いとしては,__libc_start_main関数は,__attribute__((constructor))より先に呼ばれる性質がある.

コンパイルと実行

コンパイル

1
gcc -o main main.c
1
gcc -shared -fPIC -o my_libsignal.so my_libsignal.c

実行

1
LD_PRELOAD=./my_libsignal.so ./main

上記のように実行することで,実行ファイルmainに,作成したライブラリmy_libsignal.soを動的にリンクすることができる.
実行すると前回と同じような結果になる.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
si_addr (nil)
[ucontext] 0x196db00
[REG_RIP] 0x400543
si_addr (nil)
[ucontext] 0x196db00
[REG_RIP] 0x400543
si_addr (nil)
[ucontext] 0x196db00
[REG_RIP] 0x400543
si_addr (nil)
[ucontext] 0x196db00
[REG_RIP] 0x400543
(snip)

まとめ

SIGSEGVを捕捉するライブラリを作成し,実行ファイルに動的にリンクさせた.似たような既存のライブラリにlibSegFault.soなどがある.

参考

ld_preload
sigaction
signalfd
ucontext
gregs