Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

80258
1323
114582


[리눅스커널][시그널] T32: 시그널 생성 과정 디버깅해보기 12. Signal

이전 포스팅에서 커널이 '시그널'을 어떤 방식으로 처리하는지 살펴봤습니다.
이전에 배웠던 내용을 리뷰하는 차원으로 '시그널' 처리 과정을 정리해볼까요?

1. 시그널 생성
   시그널을 받은 프로세스의 태스크 디스크립터에 시그널 정보를 써주고 해당 프로세스를 깨운다. 
2. 시그널 전달 
   시그널을 받을 프로세스는 깨어나 시그널을 처리한다. 

시그널 처리 과정에 대해 소스 코드를 분석했는데 이번에는 __send_signal() 함수에서 시그널을 생성할 때 바뀌는 시그널 자료구조에 대해서 디버깅해보는 시간을 갖겠습니다. 사실 함수 실행 흐름과 자료구조는 '이와 잇몸'의 관계와 같습니다. 조금 더 구체적으로 말씀드리면 다음과 같겠네요.

     "함수는 자료구조를 읽고 바꾸기 위해 실행한다."
     "자료구조에 저장된 값으로 함수 흐름을 제어한다."

이제 본론으로 들어가서 디버깅하려는 __send_signal() 함수 소스 코드를 같이 볼까요?
01 static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
02 enum pid_type type, int from_ancestor_ns)
03 {
04 struct sigpending *pending;
05 struct sigqueue *q;
06 int override_rlimit;
07 int ret = 0, result;
...
08 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
...
09 q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
10 if (q) {
11 list_add_tail(&q->list, &pending->list);
12 switch ((unsigned long) info) {
13 case (unsigned long) SEND_SIG_NOINFO:
14 clear_siginfo(&q->info);
15 q->info.si_signo = sig;
16 q->info.si_errno = 0;
17 q->info.si_code = SI_USER;
18 q->info.si_pid = task_tgid_nr_ns(current,
19 task_active_pid_ns(t));
20 q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
21 break;
22 case (unsigned long) SEND_SIG_PRIV:
23 clear_siginfo(&q->info);
24 q->info.si_signo = sig;
25 q->info.si_errno = 0;
26 q->info.si_code = SI_KERNEL;
27 q->info.si_pid = 0;
28 q->info.si_uid = 0;
29 break;
30 default:
31 copy_siginfo(&q->info, info);
32 if (from_ancestor_ns)
33 q->info.si_pid = 0;
34 break;
35 }
...
36 out_set:
37 signalfd_notify(t, sig);
38 sigaddset(&pending->signal, sig);
...
39 complete_signal(sig, t, type);
40 ret:
41 trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
42 return ret;
43}

먼저 시그널을 받을 프로세스 태스크 디스크립터에 시그널 정보를 써주는 코드를 볼까요?
04 struct sigpending *pending;
05 struct sigqueue *q;
...
08 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
...
09 q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
10 if (q) {
11 list_add_tail(&q->list, &pending->list);

08번째 줄: 시그널을 받을 프로세스 태스크 디스크립터의 &t->signal->shared_pending 혹은 &t->pending 필드를 읽는다.
09~11번째: 시그널 정보가 담긴 q변수 연결 리스트를 &pending->list에 추가한다. 

이번에는 시그널을 받을 프로세스에게 시그널이 전달됐다는 정보를 써줄 때 함수 흐름을 소개합니다.
complete_signal()
signal_wake_up()
signal_wake_up_state()  
 
위 함수가 실행하면 다음 동작을 수행합니다.

     "시그널을 받을 프로세스 struct thread_info 구조체 flags 필드에 _TIF_PENDINGSIG 정보를 써준다." 
 
간단히 코드를 리뷰했으니 이제 시그널을 생성했을 때 바뀌는 자료구조를 분석해보겠습니다.
먼저 디버깅을 하기 전 전제 조건을 보겠습니다.
 - 시그널을 받을 프로세스 이름: "bash"
 - 시그널을 받을 프로세스 주소: 0xFFFFFFEFC7EECC00 
 - 전달하려는 시그널: SIGCHLD(17)
 
struct sigpending * 정보를 보겠습니다.
  (struct task_struct *) [-] (struct task_struct *)0xFFFFFFEFC7EECC00 = 0xFFFFFFEFC7EECC00 -> (
    (struct thread_info) [D:0xFFFFFFEFC7EECC00] thread_info = ((long unsigned int) [D:0xFFFFFFEFC7EE
    (long int) [D:0xFFFFFFEFC7EECC50] state = 0,
    (void *) [D:0xFFFFFFEFC7EECC58] stack = 0xFFFFFF80108E8000,
    (atomic_t) [D:0xFFFFFFEFC7EECC60] usage = ((int) [D:0xFFFFFFEFC7EECC60] counter = 2),
...
    (struct nsproxy *) [D:0xFFFFFFEFC7EED3D8] nsproxy = 0xFFFFFFAF890B30D0,
    (struct signal_struct *) [D:0xFFFFFFEFC7EED3E0] signal = 0xFFFFFFEFC4CF7300 -> (
      (atomic_t) [D:0xFFFFFFEFC4CF7300] sigcnt = ((int) [D:0xFFFFFFEFC4CF7300] counter = 1),
      (atomic_t) [D:0xFFFFFFEFC4CF7304] live = ((int) [D:0xFFFFFFEFC4CF7304] counter = 1),
      (int) [D:0xFFFFFFEFC4CF7308] nr_threads = 1,
      (struct list_head) [D:0xFFFFFFEFC4CF7310] thread_head = ((struct list_head *) [D:0xFFFFFFEFC4C
      (wait_queue_head_t) [D:0xFFFFFFEFC4CF7320] wait_chldexit = ((spinlock_t) [D:0xFFFFFFEFC4CF7320
      (struct task_struct *) [D:0xFFFFFFEFC4CF7368] curr_target = 0xFFFFFFEFC7EECC00,
      (struct sigpending) [D:0xFFFFFFEFC4CF7370] shared_pending = (
        (struct list_head) [D:0xFFFFFFEFC4CF7370] list = (
          (struct list_head *) [D:0xFFFFFFEFC4CF7370] next = 0xFFFFFFEFDF691178 -> (  //<<--
            (struct list_head *) [D:0xFFFFFFEFDF691178] next = 0xFFFFFFEFC4CF7370 -> (

위 태스크 디스크립터에서 "//<<--" 라고 표시된 부분이 다음 코드 '&t->signal->shared_pending' 자료구조입니다.
shared_pending->list->next 필드가 0xFFFFFFEFDF691178를 가리키고 있습니다.

04 struct sigpending *pending;
05 struct sigqueue *q;
...
08 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;

shared_pending->list->next 필드가 0xFFFFFFEFDF691178이니 다음 명령어를 써서 struct sigqueue *q 구조체를 볼 수 있겠습니다.
v.v %t %l container_of(0xFFFFFFEFDF691178,struct sigqueue,list)
  (struct sigqueue *) [-] container_of(0xFFFFFFEFDF691178,struct sigqueue,list) = 0xFFFFFFEFDF691178
    (struct list_head) [D:0xFFFFFFEFDF691178] list = ((struct list_head *) [D:0xFFFFFFEFDF691178] ne
    (int) [D:0xFFFFFFEFDF691188] flags = 0,
    (siginfo_t) [D:0xFFFFFFEFDF691190] info = (
      (int) [D:0xFFFFFFEFDF691190] si_signo = 17, "//<<--[1]"
      (int) [D:0xFFFFFFEFDF691194] si_errno = 0,
      (int) [D:0xFFFFFFEFDF691198] si_code = 1,
      (union) [D:0xFFFFFFEFDF6911A0] _sifields = ((int [28]) [D:0xFFFFFFEFDF6911A0] _pad = (6653, 20
    (struct user_struct *) [D:0xFFFFFFEFDF691210] user = 0xFFFFFFEFDC6CAD40)

"//<<--[1]"에 표시된 것과 같이 시그널 번호가 17입니다.

이번에는 시그널을 받을 프로세스의 struct thread_info 구조체 flags 필드를 보겠습니다.
  (struct task_struct *) [-] (struct task_struct *)0xFFFFFFEFC7EECC00 = 0xFFFFFFEFC7EECC00 -> (
    (struct thread_info) [D:0xFFFFFFEFC7EECC00] thread_info = (
      (long unsigned int) [D:0xFFFFFFEFC7EECC00] flags = 0x00100001,
      (long unsigned int [7]) [D:0xFFFFFFEFC7EECC08] padding = (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0),
 
위에서 보이듯 struct thread_info 구조체 flags 필드가 0x00100001이니 _TIF_SIGPENDING 정보가 써져 있습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm64/include/asm/thread_info.h]
#define _TIF_SIGPENDING (1 << TIF_SIGPENDING)

이제 디버깅 정보를 통해 배운 내용을 정리해보겠습니다.

v.v %t %l container_of(0xFFFFFFEFDF691178,struct sigqueue,list)
  (struct sigqueue *) [-] container_of(0xFFFFFFEFDF691178,struct sigqueue,list) = 0xFFFFFFEFDF691178
    (struct list_head) [D:0xFFFFFFEFDF691178] list = ((struct list_head *) [D:0xFFFFFFEFDF691178] ne
    (int) [D:0xFFFFFFEFDF691188] flags = 0,
    (siginfo_t) [D:0xFFFFFFEFDF691190] info = (
      (int) [D:0xFFFFFFEFDF691190] si_signo = 17, "//<<--[1]"
      (int) [D:0xFFFFFFEFDF691194] si_errno = 0,
      (int) [D:0xFFFFFFEFDF691198] si_code = 1,
      (union) [D:0xFFFFFFEFDF6911A0] _sifields = ((int [28]) [D:0xFFFFFFEFDF6911A0] _pad = (6653, 20
    (struct user_struct *) [D:0xFFFFFFEFDF691210] user = 0xFFFFFFEFDC6CAD40)

     "시그널을 받으려는 프로세스의 태스크 디스크립터 signal->shared_pending->list에 
     struct sigqueue 구조체 list 플래그를 주소를 등록한다."

 
  (struct task_struct *) [-] (struct task_struct *)0xFFFFFFEFC7EECC00 = 0xFFFFFFEFC7EECC00 -> (
    (struct thread_info) [D:0xFFFFFFEFC7EECC00] thread_info = (
      (long unsigned int) [D:0xFFFFFFEFC7EECC00] flags = 0x00100001,
      (long unsigned int [7]) [D:0xFFFFFFEFC7EECC08] padding = (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0),

    "시그널을 받을 프로세스의 struct thread_info 구조체 flags는 _TIF_SIGPENDING로 바뀐다." 

이 정도로 정리하면 시그널 생성 과정에 대해서 쉽게 잊어 먹지는 않을 것 같네요.


"혹시라도 궁금한점이 있으시다면 답글로 남겨주세요. 아는 한 성실하게 답변해 드리겠습니다."



덧글

댓글 입력 영역