Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

94199
1107
135822


[리눅스커널] 시그널: __send_signal()dequeue_signal() 함수 분석 - 시그널 생성/시그널 받기 12. 시그널

커널에서 시그널 서브시스템의 핵심 동작은 다음과 같다.

   * 시그널 전송: 시그널을 받은 프로세스에게 펜딩 시그널 정보를 써주고 시그널을 받을 프로세스를 깨운다.
   * 시그널 받기: 깨어난 프로세스는 펜딩 시그널을 받아 시그널에 대한 후속 처리를 한다.  

이번 시간에는 펜딩 시그널 정보를 써주고(시그널 생성), 펜딩된 시그널을 프로세스(시그널 받음)는 읽는 과정에 
초점을 맞춰 소스를 분석해보자.   

__send_signal() 함수 분석

먼저 __send_signal() 함수를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/signal.c
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
enum pid_type type, int from_ancestor_ns)
{
struct sigpending *pending;
struct sigqueue *q;
int override_rlimit;
int ret = 0, result;
...
/* 태스크 디스크립터의 &t->signal->shared_pending 혹은 &t->pending를
    sigpending 구조체인 pending에 저장한다. 
    여기서 중요한 시사점이 있다. 전달되는 시그널의 유형에 따라 다른 필드에 펜딩 시그널을 저장한다는 점이다.
                        그룹에 전달되는 시그널이면:  &t->signal->shared_pending
         한 개 프로세스에 전달되는 시그널이면: &t->pending */

pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
...
/ * sigqueue 구조체인 q란 포인터형 지역 변수를 통해 동적 메모리를 할당 받는다. */
q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
if (q) {
/ * &pending->list 연결 리스트에 &q->list를 추가한다.
list_add_tail(&q->list, &pending->list);

이 코드에서 중요한 포인트는 시그널의 유형에 따라 서로 다른 구조에 펜딩 시그널을 써준다는 점이다.

   * 그룹에 전달되는 시그널이면:  &t->signal->shared_pending
   *  한 개 프로세스에 전달되는 시그널이면: &t->pending */

이어서 시그널을 프로세스가 받는 과정에서 펜딩 시그널을 읽는 코드를 보자.

dequeue_signal() 함수 분석

dequeue_signal() 함수의 구현부의 코드를 보자.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/signal.c
01 int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
02 {
03 bool resched_timer = false;
04 int signr;
05
06 /* We only dequeue private signals from ourselves, we don't let
07 * signalfd steal them
08 */
09 signr = __dequeue_signal(&tsk->pending, mask, info, &resched_timer);
10 if (!signr) {
11 signr = __dequeue_signal(&tsk->signal->shared_pending,
12 mask, info, &resched_timer);

위 코드를 펜딩 시그널을 읽는 관점으로 분석하면;

   * 09번째 줄: '&tsk->pending'에 있는 연결 리스트(단일 프로세스에게 시그널이 전달됐을 경우)를 통해 펜딩 시그널 정보를 읽는다.
   * 11~12번째 줄: '&tsk->pending'에 있는 연결 리스트가 비어 있다면(그룹으로 시그널이 전달됐을 경우),
                          &tsk->signal->shared_pending에 있는 연결 리스트를 통해 펜딩 시그널 정보를 읽는다.

TRACE32 펜딩 시그널 디버깅 하기

이번에는 펜딩 시그널(그룹에게 전달된 시그널)을 받은 프로세스의 자료 구조를 디버깅을 통해 알아보자.

&t->pending->list
  (struct task_struct *) (struct task_struct*)0xebd99200 = 0xEBD99200 -> (
    (long int) state = 0x0,
    (void *) stack = 0xEBDA4000,
    (atomic_t) usage = ((int) counter = 0x6),
    (unsigned int) flags = 0x00404100,
...
    (sigset_t) blocked = ((long unsigned int [2]) sig = (0x0, 0x0)),
    (sigset_t) real_blocked = ((long unsigned int [2]) sig = (0x0, 0x0)),
    (sigset_t) saved_sigmask = ((long unsigned int [2]) sig = (0x0, 0x0)),
    (struct sigpending) pending = (
      (struct list_head) list = (
        (struct list_head *) next = 0xEBD9984C,
        (struct list_head *) prev = 0xEBD9984C),
      (sigset_t) signal = ((long unsigned int [2]) sig = (0x0, 0x0))),
 
위에서 보이듯 pending->list 의 next와 prev 필드가 값은 주소를 저장하고 있다. 
즉, 링크드 리스트가 비어 있다는 이야기이다.

이번에는 signal->shared_pending->list 를 확인해보자.

&t->signal->shared_pending->list

  (struct task_struct *) (struct task_struct*)0xebd99200 = 0xEBD99200 -> (
    (long int) state = 0x0,
    (void *) stack = 0xEBDA4000,
    (atomic_t) usage = ((int) counter = 0x6),
    (unsigned int) flags = 0x00404100,
...
    (struct signal_struct *) signal = 0xEBC5F440 -> (
      (atomic_t) sigcnt = ((int) counter = 0x10),
      (atomic_t) live = ((int) counter = 0x0E),
      (int) nr_threads = 0x0E,
      (struct list_head) thread_head = ((struct list_head *) next = 0xEBD99764, (struct list_head *)
      (wait_queue_head_t) wait_chldexit = ((spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch
      (struct task_struct *) curr_target = 0xEBD99200,
      (struct sigpending) shared_pending = (
        (struct list_head) list = (
          (struct list_head *) next = 0xF1E4A510 -> (
            (struct list_head *) next = 0xEBC5F470 -> (
              (struct list_head *) next = 0xF1E4A510,
              (struct list_head *) prev = 0xF1E4A510),
            (struct list_head *) prev = 0xEBC5F470),
          (struct list_head *) prev = 0xF1E4A510),
        (sigset_t) signal = ((long unsigned int [2]) sig = (0x0100, 0x0))),
      (int) group_exit_code = 0x9,
      (int) notify_count = 0x0,

signal->shared_pending->list 필드를 보니 링크드 리스트에 뭔가 주소(0xF1E4A510, 0xEBC5F470)가 보인다.

&t->signal->shared_pending->list 주소를 통해 펜딩 시그널의 정체인 struct sigqueue를 확인해보자.
동작 원리는 아래 코드와 같다. 

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/signal.c
static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
enum pid_type type, int from_ancestor_ns)
{
struct sigpending *pending;
struct sigqueue *q;
int override_rlimit;
int ret = 0, result;
...
q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
if (q) {
/ * &pending->list 연결 리스트에 &q->list를 추가한다.
list_add_tail(&q->list, &pending->list);

v.v % container_of(0xF1E4A510,struct sigqueue,list)

  (struct sigqueue *) container_of(0xF1E4A510,struct sigqueue,list) = 0xF1E4A510 =  -> (
    (struct list_head) list = ((struct list_head *) next = 0xEBC5F470  
    (int) flags = 0 = 0x0 = '....',
    (siginfo_t) info = (
      (int) si_signo = 9 = 0x9 
      (int) si_errno = 0 = 0x0 
      (int) si_code = 0 = 0x0 
      (union) _sifields = ((int [29]) _pad = ([0] ...
    (struct user_struct *) user = 0xF1F35900  

출력 결과로 9번 SIG_KILL 시그널이 전달됐다는 사실을 알 수 있다.

이제껏 확인한 디버깅 정보의 주인공은 PID가 6269인 "mediaserver" 프로세스이다.
이번에는 프로세스 그룹에 속한 다른 프로세스의 목록을 보자.

crash> ps -g 6269
PID: 1218   TASK: ebd99200  CPU: 0   COMMAND: "mediaserver"
  PID: 1461   TASK: eac70900  CPU: 0   COMMAND: "Binder:1218_1"
  PID: 1483   TASK: eac95100  CPU: 1   COMMAND: "Binder:1218_2"
  PID: 1487   TASK: eb82bf00  CPU: 3   COMMAND: "HwBinder:1218_1"
  PID: 1489   TASK: ebc81200  CPU: 3   COMMAND: "Binder:1218_3"
  PID: 6269   TASK: eb4ee300  CPU: 1   COMMAND: "MediaClock"
  PID: 6270   TASK: d1a4a400  CPU: 3   COMMAND: "NuPlayerDriver "
  PID: 6271   TASK: d1a49b00  CPU: 2   COMMAND: "generic"
  PID: 6273   TASK: d1918000  CPU: 1   COMMAND: "NuPlayerRendere"
  PID: 7197   TASK: d23be300  CPU: 0   COMMAND: "MediaClock"
  PID: 7198   TASK: d23bda00  CPU: 1   COMMAND: "NuPlayerDriver "
  PID: 7199   TASK: d219a400  CPU: 3   COMMAND: "generic"
  PID: 7200   TASK: d219bf00  CPU: 0   COMMAND: "Binder:1218_4"
  PID: 7210   TASK: d21a8900  CPU: 3   COMMAND: "NuPlayerRendere"

위 프로세스 목록 중에서 태스크 디스크립터가 d1a4a400인 "NuPlayerDriver " 프로세스의 펜딩 시그널 정보를 확인해보자.

  (struct task_struct *) (struct task_struct*)0xd1a4a400 = 0xD1A4A400 -> (
    (long int) state = 0x0,
    (void *) stack = 0xE272C000,
    (atomic_t) usage = ((int) counter = 0x3),
    (unsigned int) flags = 0x40404040,
...
    (struct nsproxy *) nsproxy = 0xC1E1A468,
    (struct signal_struct *) signal = 0xEBC5F440 -> (
      (atomic_t) sigcnt = ((int) counter = 0x10),
      (atomic_t) live = ((int) counter = 0x0E),
      (int) nr_threads = 0x0E,
      (struct list_head) thread_head = ((struct list_head *) next = 0xEBD99764, (struct list_head *)
      (wait_queue_head_t) wait_chldexit = (
        (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) sloc
        (struct list_head) task_list = ((struct list_head *) next = 0xEBC5F464, (struct list_head *)
      (struct task_struct *) curr_target = 0xEBD99200,
      (struct sigpending) shared_pending = (
        (struct list_head) list = (
          (struct list_head *) next = 0xF1E4A510 -> (
            (struct list_head *) next = 0xEBC5F470 -> (
              (struct list_head *) next = 0xF1E4A510,
              (struct list_head *) prev = 0xF1E4A510),
            (struct list_head *) prev = 0xEBC5F470),
          (struct list_head *) prev = 0xF1E4A510),
        (sigset_t) signal = ((long unsigned int [2]) sig = (0x0100, 0x0))),
      (int) group_exit_code = 0x9,
      (int) notify_count = 0x0,
 
역시 0xF1E4A510와 0xEBC5F470 주소로 보아 &t->signal->shared_pending->list 주소를 통해 펜딩 시그널이 전달된 것을 알 수 있다.

덧글

댓글 입력 영역