아래 runq 명령어로 프로세스가 runqueue에 큐잉된 타입을 알 수 있는데요.
crash64> runq -t
 CPU 0: 27382901302089
        00000000000000  PID: 585    TASK: ffffffc071eb8b80  COMMAND: "android.hardwar"
 CPU 1: 27382901206517
        00000000000000  PID: 726    TASK: ffffffc0f6e3f300  COMMAND: "DispSync"
 CPU 2: 27382900987194
        00000000000000  PID: 18654  TASK: ffffffc089b82280  COMMAND: "pompeii_dm_router"
 CPU 3: 27382901150527
        00000000000000  PID: 16266  TASK: ffffffc089b85c00  COMMAND: "iptables"
 
아래 awk 명령어를 활용하면 각 CPU가 실행된 시간을 줄세워서 출력해요.
무엇보다 중요한 점은 얼마만큼 시간 차이가 있는지 출력하죠.
crash64> runq -t | grep CPU | sort -k3r |   awk 'NR==1{now=strtonum("0x"$3)}1{printf"%st%7.9fs time diffn", $0,(now-strtonum("0x"$3))/1000000000}'
 CPU 0: 27382901302089  0.000000000s time diff
 CPU 1: 27382901206517  0.001031024s time diff
 CPU 3: 27382901150527  0.001776480s time diff
 CPU 2: 27382900987194  0.009940724s time diff
 
 
 
이번에 crash-utility로 손 쉽게(?) wakelock 디버깅하는 방법을 소개합니다.
 
android 시스템에서 wakelock 소스를 등록할 때는 wakelocks_tree란 rbtree에 접근을 합니다.
그럼 이 정보를 바탕으로 어떤 모듈이 wakelock을 잡고 있는지 점검 하겠습니다.
 
우선 wakelocks_tree.rb_node 멤버가 갖고 있는 rbnode를 확인합니다.
crash64> p wakelocks_tree
wakelocks_tree = $2 = {
  rb_node = 0xffffffc0e397b108
}
 
wakelocks_tree에 등록한 인스턴스를 아래 wakelock_node 변수로 리다이렉트합니다.
tree -t rbtree -o wakelock.node -N 0xffffffc0e397b108 > wakelock_node
crash-utility의 가장 큰 장점은 출력 값들을 하나의 파일로 저장하고 이후 이 파일을 읽어서 입력으로 쓸 수 있습니다.
 
wakelock_node 값을 확인해볼까요? 아래 주소는 struct wakelock란 스트럽쳐로 캐스팅할 수 있어요. 역시, 인스턴스 주소로 가득차 있군요.
crash64> cat wakelock_node
ffffffc0e397b100
ffffffc0d4262200
ffffffc0c9e8e100
ffffffc066355400
ffffffc06a0a0900
ffffffc0c5cd8600
ffffffc04fddcb00
ffffffc0e397bb00
ffffffc06c55b800
ffffffc06c7c3e00
ffffffc0eb6b5100
ffffffc0d6dc2b00
ffffffc06a124500
ffffffc0c5d35300
 
struct wakelock.name 필드로 wakelock 소스 이름을 알 수 있습니다. 아래와 같이 wakelock_node 파일을 struct wakelock.name로 캐스팅을 하면 되죠. 
crash64> struct wakelock.name  < wakelock_node
  name = 0xffffffc063577000 "pompeii_pre_client_init"
  name = 0xffffffc063479080 "PowerManagerService.Display"
  name = 0xffffffc066213900 "KeyEvents"
  name = 0xffffffc0dc8e1a80 "HVDCPD_WL"
  name = 0xffffffc0e09c2200 "PowerManagerService.Broadcasts"
  name = 0xffffffc0c9325280 "SensorService_wakelock"
  name = 0xffffffc04fc6a280 "PowerManagerService.WakeLocks"
  name = 0xffffffc0d6dbef80 "pompeii"
  name = 0xffffffc0c33ebd80 "netmgr_wl"
  name = 0xffffffc0f1a0e700 "net_storage_491049870576"
  name = 0xffffffc0eb6bb680 "net_storage_491048834288"
  name = 0xffffffc0d6d7c380 "radio-interface"
  name = 0xffffffc06a0df400 "tftp_server_wakelock"
  name = 0xffffffc0c5c6ff00 "sensor_ind"
 
이렇게 코어 덤프를 받은 상태에서 crash-utility 명령어를 잘 쓰면 wakelock을 잡고 있는 소스를 좀 더 정확히 파악할 수 있어요. 
 
이번에는 어떤 원리로 이렇게 손쉽게(?) 디버깅을 했는지 소스 분석을 합시다.
 
 
wakelock을 걸면 pm_wake_lock() 함수가 호출됩니다.
int pm_wake_lock(const char *buf)
{
const char *str = buf;
struct wakelock *wl;
....
mutex_lock(&wakelocks_lock);
 
wl = wakelock_lookup_add(buf, len, true);
 
위 함수에서 wakelock_lookup_add() 함수를 호출합니다.
wakelock_lookup_add() 함수를 열어 봅시다.
1 static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
2     bool add_if_not_found)
3 {
4 struct rb_node **node = &wakelocks_tree.rb_node;
5 struct rb_node *parent = *node;
6 struct wakelock *wl;
7
8 while (*node) {
9 int diff;
10
11 parent = *node;
12 wl = rb_entry(*node, struct wakelock, node);
13 diff = strncmp(name, wl->name, len);
14 if (diff == 0) {
15 if (wl->name[len])
16 diff = -1;
17 else
18 return wl;
19 }
20 if (diff < 0)
21 node = &(*node)->rb_left;
22 else
23 node = &(*node)->rb_right;
24 }
25 if (!add_if_not_found)
26 return ERR_PTR(-EINVAL);
....
27 wl = kzalloc(sizeof(*wl), GFP_KERNEL);
28 if (!wl)
29 return ERR_PTR(-ENOMEM);
30
31 wl->name = kstrndup(name, len, GFP_KERNEL);
32 if (!wl->name) {
33 kfree(wl);
34 return ERR_PTR(-ENOMEM);
35 }
36 wl->ws.name = wl->name;
37 wakeup_source_add(&wl->ws);
38 rb_link_node(&wl->node, parent, node);
39 rb_insert_color(&wl->node, &wakelocks_tree);
40 wakelocks_lru_add(wl);
 
4번 줄은 wakelocks_tree.rb_node 변수에 접근해서 RBTree 노드에 접근하는 코드입니다.
4 struct rb_node **node = &wakelocks_tree.rb_node;
 
이번에는 8~24줄 코드를 봅시다.
8 while (*node) {
9 int diff;
10
11 parent = *node;
12 wl = rb_entry(*node, struct wakelock, node);
13 diff = strncmp(name, wl->name, len);
14 if (diff == 0) {
15 if (wl->name[len])
16 diff = -1;
17 else
18 return wl;
19 }
20 if (diff < 0)
21 node = &(*node)->rb_left;
22 else
23 node = &(*node)->rb_right;
24 }
 
rbtree에서 rb_left 혹은 rb_right 노드를 찾습니다.
 
다음은 27~31번 줄 코드를 봅시다.
27 wl = kzalloc(sizeof(*wl), GFP_KERNEL);
28 if (!wl)
29 return ERR_PTR(-ENOMEM);
30
31 wl->name = kstrndup(name, len, GFP_KERNEL);
 
27번 줄에서는 struct wakelock 구조체인 wl 지역 변수로 메모리를 할당합니다.
31번 줄은 wakelock 이름을 저장합니다.
 
37~40 줄 코드는 이 디버깅 방법과 연관된 핵심 코드입니다.
37 wakeup_source_add(&wl->ws);
38 rb_link_node(&wl->node, parent, node);
39 rb_insert_color(&wl->node, &wakelocks_tree);
40 wakelocks_lru_add(wl);
 
37번 줄 코드는 wakeup_source_add() 함수를 호출합니다.
이 wakeup_source_add() 함수는 list 타입 wakeup_sources이란 전역변수에  &ws->entry를 추가합니다.
 
다음 wakeup_source_add() 함수 8번째 줄 코드를 눈여겨 보시기 바랍니다.
static LIST_HEAD(wakeup_sources);
 
1 void wakeup_source_add(struct wakeup_source *ws)
2 {
...
3 setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
4 ws->active = false;
5 ws->last_time = ktime_get();
6
7 spin_lock_irqsave(&events_lock, flags);
8 list_add_rcu(&ws->entry, &wakeup_sources);
9 spin_unlock_irqrestore(&events_lock, flags);
10}
 
다시 wakelock_lookup_add() 함수 분석을 진행하겠습니다.
37 wakeup_source_add(&wl->ws);
38 rb_link_node(&wl->node, parent, node);
39 rb_insert_color(&wl->node, &wakelocks_tree);
 
38번 줄 코드를 보면 &wakelocks_tree이란 RB Tree에 &wl->node를 추가합니다.
 
이 코드를 제대로 이해해야 크래시 유틸리티로 디버깅을 할 수 있습니다.
 
리눅스 커널 디버깅 능력은 디버깅 툴 명령어만 외운다고 얻을 수는 없습니다.
이렇게 코드 흐름과 자료 구조를 정확히 알아야 합니다.
 
디버깅을 잘하는 개발자한테 디버깅 툴만 잘 쓴다고 개소리하며 뇌까리는 무식한 관리자들을 보면 이럴 때 이 블로그 글을 보여주세요. 아니면 그냥 무시하던가요.
아래 글에서 stack canary에 대한 내용을 다뤘습니다. 스택을 깨는 지 점검하는 루틴인데요.
 
이번에는 다른 디버깅 패치를 작성해서 어떤 루틴이 스택 오염을 시켰는지 점검해보겠습니다.
 
우선 스택이 깨지는 순서를 살펴보겠습니다.
 
1. 아래 함수가 처음 실행될 때 순서로 스택을 푸쉬합니다.
current_sp-0x1c---  R14 // 0xC06FAE8C 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  R3
current_sp-0x14---  R4 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
current_sp---------      0xC06FAE80 주소에서의 스택 주소
 
해당 코드는 아래와 같습니다.
NSR:C06FAE80|E1A0C00D  touch_irq_handler:  cpy     r12,r13
NSR:C06FAE84|E92DD818                      push    {r3-r4,r11-r12,r14,pc}
NSR:C06FAE88|E24CB004                      sub     r11,r12,#0x4     ; r11,r12,#4
NSR:C06FAE8C|E52DE004                      push    {r14}
 
2. touch_irq_handler() 함수에서 bald_bsp() 란 함수를 호출했다고 가정할께요.
NSR:C06FDE84|E1A0C00D  bald_bsp:  cpy     r12,r13
NSR:C06FDE88|E92DD818                      push    {r3-r4, r14}
 
bald_bsp()의  0xC06FDE88 코드에서 스택 푸쉬를 하고 나면 아래와 같이 스택 푸쉬를 합니다.
current_sp-0x28----  R3  //<<-- bald_bsp()의  0xC06FDE88 코드에서 스택 푸쉬 후 스택 주소
current_sp-0x24----  R4
current_sp-0x20----  R14 // touch_irq_handler에서 bald_bsp()를 호출했으니 touch_irq_handler 함수 내 주소일 것임
current_sp-0x1c---  R14 // 0xC06FAE8C(touch_irq_handler 내) 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  R3
current_sp-0x14---  R4 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
 
3. bald_bsp() 함수에서 코드를 누군가 잘못 짜서 string copy를 out-of-bound로 처리하면 아래와 같이 스택이 오염됩니다.
current_sp-0x28----  "A"
current_sp-0x24----  "B"
current_sp-0x20---- "C" // R14 // touch_irq_handler에서 bald_bsp()를 호출했으니 touch_irq_handler 함수 내 주소일 것임
current_sp-0x1c---  "D" // 0xC06FAE8C(touch_irq_handler 내) 실행 시 스택 주소(스택 푸쉬 후)
current_sp-0x18---  "E // "R3가 저장된 주소
current_sp-0x14---  "F" // R4 가 저장된 주소 
current_sp-0x10---  R11
current_sp-0xc----  R12
current_sp-0x8----  LR
current_sp-0x4----  PC
 
이제 패치 코드를 작성해 볼 시간입니다. 아래 패치 코드는 touch_irq_handler 함수가 실행되기 직전 스택 주소를 얻어서 스택 덤프를 출력합니다.
diff --git a/drivers/input/touchscreen/touch_core.c b/drivers/input/touchscreen/touch_core.c
index 9cbf6ad..989c6d8 100644
--- a/drivers/input/touchscreen/touch_core.c
+++ b/drivers/input/touchscreen/touch_core.c
@@ -168,10 +168,24 @@ static void touch_core_initialize(struct touch_core_data *ts)
        touch_report_all_event(ts);
 }
 
+#define GET_LR *((unsigned long*)(current_sp+0x20))
+#define ADDRESS_OFFSET 4 // this is 4 bit architecture
+static unsigned int push_stack_size = 10;  // 0x18 in hexa format
+
+static unsigned long stack_array = 0;
+static unsigned long stack_address_offset = 0;
+
 irqreturn_t touch_irq_handler(int irq, void *dev_id)
 {
+       register unsigned long current_sp asm ("sp");
        struct touch_core_data *ts = (struct touch_core_data *) dev_id;
 
+       printk("==== start dump stack dump ==== \n");
+       for( stack_array = 0; stack_array < push_stack_size; stack_array++) {
+               stack_address_offset = stack_array * ADDRESS_OFFSET;
+               printk("[0x%lx] = 0x%lx \n", current_sp + stack_address_offset, *((unsigned long*)(current_sp + stack_address_offset)) );
+       }
+
        bald_bsp();
 
+       printk("==== start dump stack dump ==== \n");
+       for( stack_array = 0; stack_array < push_stack_size; stack_array++) {
+               stack_address_offset = stack_array * ADDRESS_OFFSET;
+               printk("[0x%lx] = 0x%lx \n", current_sp + stack_address_offset, *((unsigned long*)(current_sp + stack_address_offset)) );
+       }
+
 
위 패치의 핵심 코드는 #define GET_LR *((unsigned long*)(current_sp+0x20))인데, 패치 코드를 반영하고 나서 스택 푸쉬되는 바이트를 0x20로 계산했습니다.
   NSR:C06FAE80|E1A0C00D  touch_irq_handler:  cpy     r12,r13
   NSR:C06FAE84|E92DD818                      push    {r3-r4,r11-r12,r14,pc}
   NSR:C06FAE88|E24CB004                      sub     r11,r12,#0x4     ; r11,r12,#4
   NSR:C06FAE8C|E52DE004                      push    {r14}
 
 위 패치를 빌드해서 커널 로그를 받아보면 아래와 같이 bald_bsp() 함수가 호출된 후 스택 덤프가 깨져 있습니다.
<6>[  154.878931 / 02-06 16:59:35.892][0] ==== start dump stack dump ==== 
<6>[  154.878960 / 02-06 16:59:35.892][0] [0xc5233b58] = 0xc06fae80 
<6>[  154.878976 / 02-06 16:59:35.892][0] [0xc5233b5c] = 0xc4226e80 
<6>[  154.878992 / 02-06 16:59:35.892][0] [0xc5233b60] = 0xe1 
<6>[  154.879007 / 02-06 16:59:35.892][0] [0xc5233b64] = 0x0 
<6>[  154.879022 / 02-06 16:59:35.892][0] [0xc5233b68] = 0xc5233bb4 
<6>[  154.879037 / 02-06 16:59:35.892][0] [0xc5233b6c] = 0xc5233b78 
<6>[  154.879052 / 02-06 16:59:35.892][0] [0xc5233b70] = 0xc0085440 
<6>[  154.879067 / 02-06 16:59:35.892][0] [0xc5233b74] = 0xc06fae8c 
<6>[  154.879083 / 02-06 16:59:35.892][0] [0xc5233b78] = 0x20000193 
<6>[  154.879098 / 02-06 16:59:35.892][0] [0xc5233b7c] = 0xc0f72564 
<6>[  154.895630 / 02-06 16:59:35.912][0] ==== start dump stack dump ==== 
<6>[  154.895660 / 02-06 16:59:35.912][0] [0xc5233e58] = 0xA 
<6>[  154.895676 / 02-06 16:59:35.912][0] [0xc5233e5c] = 0xB 
<6>[  154.895691 / 02-06 16:59:35.912][0] [0xc5233e60] = 0xC 
<6>[  154.895706 / 02-06 16:59:35.912][0] [0xc5233e64] = 0xD 
<6>[  154.895723 / 02-06 16:59:35.912][0] [0xc5233e68] = 0xE 
<6>[  154.895740 / 02-06 16:59:35.912][0] [0xc5233e6c] = 0xF 
<6>[  154.895755 / 02-06 16:59:35.912][0] [0xc5233e70] = 0x10 
<6>[  154.895771 / 02-06 16:59:35.912][0] [0xc5233e74] = 0x11 
<6>[  154.895786 / 02-06 16:59:35.912][0] [0xc5233e78] = 0x12 
<6>[  154.895801 / 02-06 16:59:35.912][0] [0xc5233e7c] = 0x13 
 
이런 방식으로 분석하면 어떤 함수가 스택을 깨는 지 점검할 수 있습니다.
 

안드로이드에서 wake lock이란 기능이 있습니다. 그런데 대부분 wake lock을 어떤 모듈이 잡고 있어서 슬립에 못 들어가는 문제가 생기죠. 이럴 때 프로파일링하면 좋은 디버그 패치를 소개합니다.

diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
old mode 100644
new mode 100755
index 7dbfe1a..2ca0964
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -54,6 +54,7 @@ static DEFINE_SPINLOCK(events_lock);
 static void pm_wakeup_timer_fn(unsigned long data);
 
 static LIST_HEAD(wakeup_sources);
+static LIST_HEAD(wakeup_sources_shadow);
 
 static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue);
 
@@ -134,6 +135,17 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
  wakeup_source_destroy(container_of(head, struct wakeup_source, rcu));
 }
 
+struct wakeup_source_shadow {
+ struct list_head entry;
+ const char *name;
+ struct task_struct *p;
+ struct wakeup_source *ws;
+ ktime_t add_ts;
+ ktime_t rmv_ts;
+ int ws_event; /* 1 for add, 2 for remove */
+}; 
+
+
 /**
  * wakeup_source_add - Add given object to the list of wakeup sources.
  * @ws: Wakeup source object to add to the list.
@@ -141,7 +153,8 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
 void wakeup_source_add(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
@@ -152,6 +165,19 @@ void wakeup_source_add(struct wakeup_source *ws)
 
  spin_lock_irqsave(&events_lock, flags);
  list_add_rcu(&ws->entry, &wakeup_sources);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] add hit : %p, %s\n", __func__, ws, ws->name);
+ ws_sh = kmalloc(sizeof(*ws_sh), GFP_ATOMIC);
+ if (ws_sh) {
+ ws_sh->ws = ws;
+ ws_sh->p = current;
+ ws_sh->name = ws->name;
+ ws_sh->add_ts = ws_sh->rmv_ts = ws->last_time;
+ ws_sh->ws_event = 1;
+ list_add_rcu(&ws_sh->entry, &wakeup_sources_shadow);
+ dump_stack();
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
 }
 EXPORT_SYMBOL_GPL(wakeup_source_add);
@@ -163,12 +189,25 @@ EXPORT_SYMBOL_GPL(wakeup_source_add);
 void wakeup_source_remove(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
  spin_lock_irqsave(&events_lock, flags);
  list_del_rcu(&ws->entry);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] rmv hit : %p, %s\n", __func__, ws, ws->name);
+ list_for_each_entry(ws_sh, &wakeup_sources_shadow, entry) {
+ if (ws_sh->ws == ws) {
+ ws_sh->rmv_ts = ktime_get();
+ ws_sh->ws_event = 2;
+ ws_sh->p = current;
+ dump_stack();
+ break;
+ }
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
  synchronize_rcu();
 }
 
런큐에 CFS 스케쥴러로 큐잉된 상태로 기다리는 프로세스 목록은 어디서 찾을 수 있을까요?
per-cpu 타입의 runqueues.cfs_tasks 멤버를 찾으면 됩니다.
 
다음은 CPU4 runqueue의 예시입니다.
  (struct rq *) [-] (struct rq*)(((void*)&runqueues)+__per_cpu_offset[4]) = 0xFFFFFFC73E0F2900 -> (
    (raw_spinlock_t) [D:0xFFFFFFC73E0F2900] lock = ((arch_spinlock_t) [D:0xFFFFFFC73E0F2900] raw_lock = ((u16) [D:0xFFFF
    (unsigned int) [D:0xFFFFFFC73E0F2918] nr_running = 0x3,
 //...
    (int) [D:0xFFFFFFC73E0F3298] cpu = 0x4,
    (int) [D:0xFFFFFFC73E0F329C] online = 0x1,
    (struct list_head) [D:0xFFFFFFC73E0F32A0] cfs_tasks = (
      (struct list_head *) [D:0xFFFFFFC73E0F32A0] next = 0xFFFFFFC67B0CE9A8 -> (
        (struct list_head *) [D:0xFFFFFFC67B0CE9A8] next = 0xFFFFFFC73E0F32A0,
        (struct list_head *) [D:0xFFFFFFC67B0CE9B0] prev = 0xFFFFFFC73E0F32A0),
      (struct list_head *) [D:0xFFFFFFC73E0F32A8] prev = 0xFFFFFFC67B0CE9A8),
 
runqueues.cfs_tasks.next는 해당 프로세스의 struct task_struct,se.group_node 멤버로 등록돼 있습니다.
따라서 다음 커맨드를 입력하면 프로세스 태스크 디스크립터 주소를 알아낼 수 있습니다.
  (struct task_struct *) container_of(0xFFFFFFC67B0CE9A8struct task_struct,se.group_node) = 0xFFFFFFC67B0CE900 -> (
    (struct thread_info) thread_info = ((long unsigned int) flags = 0x00400000, (mm_segment_t) addr_limit = 0x0000008000000000, (int) pre
    (long int) state = 0x0,
    (void *) stack = 0xFFFFFFC6A295C000,
//...
    (struct cred *) ptracer_cred = 0x0,
    (struct cred *) real_cred = 0xFFFFFFC72D06E180,
    (struct cred *) cred = 0xFFFFFFC72D06E180,
    (char [16]) comm = "visualizer capt",
config.t32 파일에 다음 명령어를 입력하면 Trace32 폰트 색상이 바뀝니다.
PALETTE 0 = 56 56 56
PALETTE 1 = 221 221 221
PALETTE 4 = 128 0 255
PALETTE 11 = 255 0 0
PALETTE 18 = 255 128 64
PALETTE 27 = 255 128 64
PALETTE 48 = 128 128 0
PALETTE 50 = 255 128 64.
 
Trace32를 열고 setup.color을 입력해서 폰트나 텍스트 상자 색상을 바꿀 수도 있습니다.
 
cmm 스크립트로는 다음과 같은 명령어를 입력하면 됩니다.
 
//PALETTE 0 = 56 56 56
SETUP.COLOR 0. 56. 56. 56.
 
//PALETTE 1 = 221 221 221
SETUP.COLOR 1. 221. 221. 221.
 
//PALETTE 4 = 128 0 255
SETUP.COLOR 4. 128. 0. 255.
 
//PALETTE 11 = 255 0 0
SETUP.COLOR 11. 255. 0. 0.
 
//PALETTE 18 = 255 128 64
SETUP.COLOR 18. 255. 128. 64.
 
//PALETTE 27 = 255 128 64
SETUP.COLOR 27. 255. 128. 64.
 
//PALETTE 48 = 128 128 0
SETUP.COLOR 48. 128. 128. 0.
 
//PALETTE 50 = 255 128 64.
SETUP.COLOR 50. 255. 128. 64.
 
이번 시간에는 wakeup_sources이란 링크드 리스트를 통해 wakelock 디버깅을 합시다.
이전에는 crash-utility를 썻는데 이번에는 Trace32를 쓰겠습니다.
 
개발자는 다양한 툴을 써야 한 가지 툴에 종속된 노예 개발자가 되는 것을 피할 수 있습니다.
또한 각 툴의 장점을 잘 활용할 수도 있습니다.
 
먼저, 다음 T32 명령어를 입력해서 offsetof와 container_of 매크로를 생성합시다.
sYmbol.NEW.MACRO offsetof(type,member) ((int)(&((type*)0)->member))
sYmbol.NEW.MACRO container_of(ptr,type,member) ((type *)((char *)(ptr)-offsetof(type,member)))
 
wakelock을 잡은 모듈들은 wakeup_sources 이란 전역 변수에 등록돼 있습니다.
우선 wakeup_sources 변수를 봅시다.
v.v %h %t wakeup_sources
  (static struct list_head) wakeup_sources = (
    (struct list_head *) next = 0xFFFFFFC5FD92EA88 -> (
      (struct list_head *) next = 0xFFFFFFC5FD92FC88 -> (
        (struct list_head *) next = 0xFFFFFFC541226488,
        (struct list_head *) prev = 0xFFFFFFC5FD92EA88),
      (struct list_head *) prev = 0xFFFFFF97D92B4D00),
    (struct list_head *) prev = 0xFFFFFFC62D3EDB88)
 
계속 struct list_head->next로 리스트들이 연결돼 있습니다.
 
이제 앞에서 생성한 container_of 매크로를 쓸 순간입니다. 다음과 같이 명령어를 입력합시다.
v.v %h %t %s  container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry)
  (struct wakeup_source *) container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry) = 0xFFFFFFC5FD92EA80 -> (
    (char *) name = 0xFFFFFFC55DEE3280 -> "ipc00001269_1988_HwBinder:682_1",
    (struct list_head) entry = ((struct list_head *) next = 0xFFFFFFC5FD92FC88, (struct list_head *) prev = 0xFFF
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x298F, (u16)
    (struct wake_irq *) wakeirq = 0x0,
    (struct timer_list) timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0x0, (struct hlist_nod
    (long unsigned int) timer_expires = 0x0,
    (ktime_t) total_time = ((s64) tv64 = 0x0),
    (ktime_t) max_time = ((s64) tv64 = 0x0),
    (ktime_t) last_time = ((s64) tv64 = 0x000040E1FC6B0DF2),
    (ktime_t) start_prevent_time = ((s64) tv64 = 0x0),
    (ktime_t) prevent_sleep_time = ((s64) tv64 = 0x0),
    (long unsigned int) event_count = 0x0,
    (long unsigned int) active_count = 0x0,
    (long unsigned int) relax_count = 0x0,
    (long unsigned int) expire_count = 0x0,
    (long unsigned int) wakeup_count = 0x0,
    (long unsigned int) pending_count = 0x0,
    (bool:1) active = FALSE,
    (bool:1) autosleep_enabled = FALSE)
 
위 멤버 변수 중 active 변수가 FALSE이니 "ipc00001269_1988_HwBinder:682_1"이란 wakeup source가 wakelock을 잡고 있지 않습니다.
 
v.v %h %t %s  container_of(0xFFFFFFC5FD92EA88,struct wakeup_source,entry)
 
위와 같은 명령어를 쓴 이유는, struct wakeup_source이란 구조체에 entry이란 struct list_head 타입 멤버 변수가
wakeup_sources이란 링크드 리스트 전역 변수 wakeup_sources->next로 등록됐기 때문입니다.
 
해당 구조체를 봅시다.
struct wakeup_source {
const char  *name;
struct list_head entry;
spinlock_t lock;
struct wake_irq *wakeirq;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
bool active:1;
bool autosleep_enabled:1;
};
 
쉽게 설명하면 아래 방식으로 wakeup_source가 wakeup_sources 리스트에 등록합니다.
1st wakeup source 등록
wakeup_sources->next = struct wakeup_source->entry
 
2nd wakeup source등록
wakeup_sources->next->next = struct wakeup_source->entry
 
개발자분들이여, 임베디드 동네의 최강의 툴인 Trace32를 잘 활용해서 일찍 퇴근합시다.
 
유저 공간에서 zygote가 강제 종료되면서 부팅을 못하는 상황입니다. 커널 로그로 아래 메시지를 볼 수 있습니다.
아래 로그는 init 프로세스가 zygote에 SIGABRT(6) 시그널을 전달해서 zygote를 종료시키고 있습니다.
[   46.116831 / 01-02 01:20:24.859][0] init: Service 'zygote' (pid 1777) killed by signal 6
[   46.124107 / 01-02 01:20:24.869][1] init: Service 'zygote' (pid 1777) killing any children in process group
 
그럼 이 동작을 할 때 커널 관점으로 어떤 코드가 수행되는지 살펴보겠습니다.
 
zygote는 커널 공간에서 "main" 이란 쓰레드로 수행됩니다. 그래서 아래 코드를 눈여겨 보면 __send_signal()/send_sigqueue 함수에서 전달하는 struct task_struct *t 가 시그널 전달 받는 프로세스입니다. 이 프로세스의 comm이란 멤버가 프로세스 이름을 담고 있습니다.
 
참고로 아래와 같이 ftrace log를 설정하고, 강제로 zygote를 종료하는 테스트를 수행했습니다.
adb shell "echo 1 > /d/tracing/events/sched/sched_process_exit/enable"
adb shell "echo 1 > /d/tracing/events/sched/sched_process_fork/enable"
 
adb shell "echo 1 > /d/tracing/events/signal/enable"
 
kill -6 918, 즉 kill -6 [pid]로 자이고트에 SIGABRT 시그널을 전달하는 것입니다.
root      918   1     1111816 81936 poll_sched b2ae1ec4 S zygote
austin:/ # kill -6 918
 
그럼 다음과 같은 ftrace 로그를 볼 수 있습니다. main이란 프로세스가 sig 6 SIGABT를 맞고 결국 종료됩니다.
<...>-456   [005] d..2  1186.470605: signal_generate: sig=6 errno=0 code=-6 comm=main pid=918 grp=0 res=0
<...>-918   [005] d..2  1186.470786: signal_deliver: sig=6 errno=0 code=-6 sa_handler=0 sa_flags=14000000
<...>-6202  [007] d..2  1186.470881: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6205  [005] d..2  1186.470895: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203  [007] d..2  1186.470919: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6204  [005] d..2  1186.470969: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0
<...>-6203  [007] ...1  1186.471086: sched_process_exit: comm=FinalizerDaemon pid=6203 prio=120
<...>-918   [005] ...1  1186.471092: sched_process_exit: comm=main pid=918 prio=120
<...>-6204  [005] ...1  1186.471780: sched_process_exit: comm=FinalizerWatchd pid=6204 prio=120
<...>-6202  [007] ...1  1186.471798: sched_process_exit: comm=ReferenceQueueD pid=6202 prio=120
 
그래서 프로세스 이름이 main일 때 강제 커널 패닉을 유발하는 코드를 작성했습니다.
diff --git a/kernel/signal.c b/kernel/signal.c
index 7408330..3157014 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1031,6 +1031,12 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
        assert_spin_locked(&t->sighand->siglock);
 
        result = TRACE_SIGNAL_IGNORED;
+       if (!strcmp(t->comm, "main")) {
+               show_stack(NULL, NULL);
+               printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+               BUG();
+       }
+
        if (!prepare_signal(sig, t,
                        from_ancestor_ns || (info == SEND_SIG_FORCED)))
                goto ret;
@@ -1120,6 +1126,7 @@ out_set:
        sigaddset(&pending->signal, sig);
        complete_signal(sig, t, group);
 ret:
+       printk("[+][Pompeii] proc %s sig: %d \n", t->comm, sig);
        trace_signal_generate(sig, info, t, group, result);
        return ret;
 }
@@ -1568,6 +1575,15 @@ int send_sigqueue(struct sigqueue *q, struct task_struct *t, int group)
        unsigned long flags;
        int ret, result;
 
+
+       if (!strcmp(t->comm, "main")) {
+               show_stack(NULL, NULL);
+               printk("[+][Pompeii] proc %s is ignored \n", t->comm);
+               show_stack(NULL, NULL);
+
+               BUG();
+       }
+
 
위 코드를 반영하고 타겟 디바이스에 다운로드를 했습니다. 역시 예상대로 부팅 후 20초 정도 시간이 지나자 커널 패닉이 발생했습니다.
 
유저 영역 콜스택까지 확인하니 아래 함수 흐름으로 __send_signal 함수가 호출됩니다.
참고로 심볼 정보가 포함된 libc.so 파일은 symbols\system\lib64에 위치한 libc.so로 로딩해야 한다는 점을 잊지 마세요.
-000|do_undefinstr(regs = 0xFFFFFFC001714630)
-001|__send_signal.constprop.27(sig = 1865235760, info = 0xFFFFFFC06F2D3E40, t = 0xFFFFFFC0000AE488, grou
-002|send_signal(inline)
-002|do_send_sig_info(sig = 6, info = 0xFFFFFFC06F2D3E40, p = 0xFFFFFFC06F2C8000, ?)
-003|do_send_specific(tgid = 748, ?, sig = 6, info = 0xFFFFFFC06F2D3E40)
-004|do_tkill(tgid = 748, pid = 748, sig = 6)
-005|SYSC_tgkill(inline)
-005|sys_tgkill(?, ?, ?)
-006|el0_svc_naked(asm)
 -->|exception
-007|tgkill(asm)
-008|pthread_kill(?, sig = 6)
-009|raise(sig = 748)
-010|abort()
-011|android_log_write_string8_len(?, ?, ?)
 
유저 공간에서 어떤 일이 있었는지 확인해보니 아래 흐름으로 abort가 발생했습니다.
abort -> raise -> pthread_kill -> tgkill
 
정리하면, 유저 영역에서 abort가 발생하면 커널 영역에 signal 6를 전달함을 알 수 있죠.
 
그런데 dump state 로그를 확인하면 아래 콜스택을 볼 수 있습니다.
/system/lib64/libc.so (tgkill+8)
/system/lib64/libc.so (pthread_kill+64)
/system/lib64/libc.so (raise+24)
/system/lib64/libc.so (abort+52)
/system/lib64/libart.so art::Runtime::Abort()                                                                          /system/lib64/libart.so art::LogMessage::~LogMessage()                                                                  /system/lib64/libart.so art::Thread::AssertNoPendingException()    
/system/lib64/libart.so art::ClassLinker::FindClass(art::Thread*, char const*, art::Handle<art::mirror::ClassLoader>)    /system/lib64/libart.so art::ClassLinker::FindArrayClass(art::Thread*, art::mirror::Class**)                                  /system/lib64/libart.so art::JNI::NewObjectArray(_JNIEnv*, int, _jclass*, _jobject*)
/system/lib64/libandroid_runtime.so                                                                                              /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)                 
/system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)                            
/system/framework/oat/arm64/services.odex (offset 0xd1b000)
 
C++ 코드에서 클래스 메쏘드로 구현된 함수들은 심볼들이 이상하게 보입니다.
아래 폴더에 있는 바이너리 유틸리티 프로그램을 쓰면 원래 심볼로 보여줍니다. c++filt 옵션을 기억합시다. 
android/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-androidkernel-nm              ./aarch64-linux-androidkernel-nm libart.so | c++filt > sym_libart.c
 
참고로 dump state 원래 로그는 아래와 같습니다. 알 수 없는 _ZN3art7Runtime5AbortEv 심볼이 보이죠.
01-03 00:37:24.599  1721  1721 F DEBUG   : backtrace:
01-03 00:37:24.599  1721  1721 F DEBUG   :     #00 pc 000000000006b8c8  /system/lib64/libc.so (tgkill+8)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #01 pc 0000000000068d4c  /system/lib64/libc.so (pthread_kill+64)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #02 pc 00000000000242b8  /system/lib64/libc.so (raise+24)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #03 pc 000000000001ccd4  /system/lib64/libc.so (abort+52)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #04 pc 000000000042c5d0  /system/lib64/libart.so (_ZN3art7Runtime5AbortEv+352)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #05 pc 00000000000e4b24  /system/lib64/libart.so (_ZN3art10LogMessageD2Ev+1204)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #06 pc 000000000044eaf4  /system/lib64/libart.so (_ZNK3art6Thread24AssertNoPendingExceptionEv+836)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #07 pc 0000000000123ec8  /system/lib64/libart.so (_ZN3art11ClassLinker9FindClassEPNS_6ThreadEPKcNS_6HandleINS_6mirror11ClassLoaderEEE+68)
01-03 00:37:24.599  1721  1721 F DEBUG   :     #08 pc 000000000011f170  /system/lib64/libart.so (_ZN3art11ClassLinker14FindArrayClassEPNS_6ThreadEPPNS_6mirror5ClassE+744)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #09 pc 0000000000348c3c  /system/lib64/libart.so (_ZN3art3JNI14NewObjectArrayEP7_JNIEnviP7_jclassP8_jobject+652)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #10 pc 0000000000142254  /system/lib64/libandroid_runtime.so
01-03 00:37:24.600  1721  1721 F DEBUG   :     #11 pc 0000000001baa5d0  /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.nativeInitialize+124)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #12 pc 0000000001ba9e80  /system/framework/arm64/boot-framework.oat (offset 0x1663000) (android.hardware.location.ContextHubService.<init>+300)
01-03 00:37:24.600  1721  1721 F DEBUG   :     #13 pc 0000000000dc8474  /system/framework/oat/arm64/services.odex (offset 0xd1b000)
 
정리하면 유저 공간에서 Abort가 떨어지면 abort() -> raise() -> pthread_kill() -> tgkill() 흐름으로 sys_tgkill란 시스템 콜이 호출되어 커널 공간에서는 __send_signal 함수가 호출됩니다.
 
위 동작은 모두 Aarch64 아키텍처 환경에서 설명을 드린 건데요. Aarch32 아키텍처는 약간 동작이 다릅니다.
-000|__do_user_fault(tsk = 0x46, addr = 0, fsr = 5, sig = 11, code = 0, regs = 0x0)
-001|__do_kernel_fault(inline)
-001|do_page_fault(addr = 3883189760, fsr = 5, regs = 0xE5BEFFB0)
-002|do_translation_fault(?, fsr = 3854499232, ?)
-003|do_DataAbort(addr = 0, fsr = 5, regs = 0xE5BEFFB0)
-004|__dabt_usr(asm)
 
arm32 비트 아키텍처에선 유저 영역에서 어보트가 떨어지면 __dabt_usr란 익셉션 벡터가 실행되고, (물론 유저 공간에서 수행 중인 레지스터를 스택에 푸시합니다.)
NSR:C0F733E0|E24DD048  __dabt_usr:       sub     r13,r13,#0x48    ; r13,r13,#72
NSR:C0F733E4|E98D1FFE                    stmib   r13,{r1-r12}
NSR:C0F733E8|EE117F10                    mrc     p15,0x0,r7,c1,c0,0x0   ; p15,0,r7,c1,c0,0 (system control)
NSR:C0F733EC|E51F80B4                    ldr     r8,0xC0F73340
NSR:C0F733F0|E8900038                    ldm     r0,{r3-r5}
NSR:C0F733F4|E28D003C                    add     r0,r13,#0x3C     ; r0,r13,#60
NSR:C0F733F8|E3E06000                    mvn     r6,#0x0          ; r6,#0
NSR:C0F733FC|E58D3000                    str     r3,[r13]
NSR:C0F73400|E5988000                    ldr     r8,[r8]
NSR:C0F73404|E8800070                    stm     r0,{r4-r6}
NSR:C0F73408|E9406000                    stmdb   r0,{r13-r14}^
NSR:C0F7340C|E1380007                    teq     r8,r7
NSR:C0F73410|1E018F10                    mcrne   p15,0x0,r8,c1,c0,0x0   ; p15,0,r8,c1,c0,0 (system control)
NSR:C0F73414|E3A0B000                    mov     r11,#0x0         ; r11,#0
NSR:C0F73418|EBC5BDA8                    bl      0xC00E2AC0       ; trace_hardirqs_off
NSR:C0F7341C|E1A0200D                    cpy     r2,r13
NSR:C0F73420|EBC2BC6E                    bl      0xC00225E0       ; v7_early_abort
 
v7_early_abort란 레이블에서 MMU의 fault register를 r1 레지스터로 읽습니다. 
NSR:C00225E0|EE151F10  v7_early_abort:     mrc     p15,0x0,r1,c5,c0,0x0   ; p15,0,r1,c5,c0,0 (data fault status)
NSR:C00225E4|EE160F10                      mrc     p15,0x0,r0,c6,c0,0x0   ; p15,0,r0,c6,c0,0 (data fault address)
NSR:C00225E8|EAFF97CF                      b       0xC000852C       ; do_DataAbort
 
위 코드에서 "mrc     p15,0x0,r1,c5,c0,0x0" 명령어를 잘 기억하세요.
 
결국 MMU fault register에서 읽은 값에 따라  fsr_info[5] 전역변수에 이미 정의된 do_translation_fault 함수를 호출하는군요.
  (static struct fsr_info [32]) fsr_info = (
    [0] = (
      (int (*)()) fn = 0xC001F58C = do_bad,
      (int) sig = 11,
      (int) code = 0,
      (char *) name = 0xC13F1AD6 = kallsyms_token_index+0x4FC6),
    [1] = (
      (int (*)()) fn = 0xC0021628 = do_alignment,
      (int) sig = 7,
      (int) code = 196609,
      (char *) name = 0xC13F1AE7 = kallsyms_token_index+0x4FD7),
    [2] = (
      (int (*)()) fn = 0xC00184B4 = hw_breakpoint_pending,
      (int) sig = 5,
      (int) code = 196612,
      (char *) name = 0xC13F0451 = kallsyms_token_index+0x3941),
    [3] = (
      (int (*)()) fn = 0xC001F58C = do_bad,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
    [4] = (
      (int (*)()) fn = 0xC0F7528C = do_translation_fault,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F17D9 = kallsyms_token_index+0x4CC9),
    [5] = (
      (int (*)()) fn = 0xC0F7528C = do_translation_fault,
      (int) sig = 11,
      (int) code = 196609,
      (char *) name = 0xC13F193F = kallsyms_token_index+0x4E2F), 
 
    [6] = ((int (*)()) fn = 0xC001F58C = do_bad, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F17F3 = kallsyms_token_index+0x4CE3),
    [7] = ((int (*)()) fn = 0xC0F74E4C = do_page_fault, (int) sig = 11, (int) code = 196609, (char *) name = 0xC13F1970 = kallsyms_token_index+0x4E60),
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 
이번 시간에는 크래시 유틸리티로 라덱스 트리를 디버깅하는 방법을 소개합니다.
 
라딕스 트리(Radix Tree)를 보기 위한 명령어
 
 
크래시 유틸리티로 라덱스 트리 노드를 보기 위한 명령어 포멧은 다음과 같습니다.
 
tree -t radix -N (struct radix_tree_node *) 구조체 주소
 
 
예제 명령어 및 결과 ( struct radix_tree_node 구조체 주소가 0xFFFFFFFF3A806E79 인 경우)
 
crash> tree -t radix -N 0xFFFFFFFF3A806E79
ffffffff3f53c180
ffffffff3f53c4c0
ffffffff3f555180
ffffffff3f5554c0
ffffffff3f56e180
ffffffff3f56e4c0
ffffffff3f587180
ffffffff3f5874c0
ffffffff3f5a0180
ffffffff3f5a04c0
ffffffff3f5b9180
ffffffff3f5b94c0
ffffffff3f5d2180
ffffffff3f5d24c0
ffffffff3f5eb180
ffffffff3f5eb4c0
fffffffe4683d480
ffffffff3657a480
 
 
Case Study: worker_pool_idr 전역변수로 라덱스 트리 디버깅해보기 
 
 
워커풀은 라덱스 트리로 구성돼 있습니다.
 
#define for_each_pool(pool, pi)                     \
    idr_for_each_entry(&worker_pool_idr, pool, pi)          \
        if (({ assert_rcu_or_pool_mutex(); false; })) { }   \
        else
 
 
worker_pool_idr 전역변수 타입은 struct idr worker_pool_idr입니다.
 
crash64> whatis worker_pool_idr
struct idr worker_pool_idr;
crash64>
 
 
struct idr 구조체 idr_rt 필드 타입은 struct radix_tree_root입니다.
 
crash64> struct idr
struct idr {
    struct radix_tree_root idr_rt;
    unsigned int idr_base;
    unsigned int idr_next;
}
 
 
worker_pool_idr.idr_rt 필드의 라덱스 트리 노드 주소는 0xffffffff3a806e79입니다.
 
crash64> p worker_pool_idr.idr_rt.rnode
$2 = (struct radix_tree_node *) 0xffffffff3a806e79
 
 
다음 명령어를 입력하면 라덱스 트리 모든 노드를 볼 수 있습니다.
 
crash64_kaslr> tree -t radix -N 0xFFFFFFFF3A806E79
ffffffff3f53c180
ffffffff3f53c4c0
ffffffff3f555180
ffffffff3f5554c0
ffffffff3f56e180
ffffffff3f56e4c0
ffffffff3f587180
ffffffff3f5874c0
ffffffff3f5a0180
ffffffff3f5a04c0
ffffffff3f5b9180
ffffffff3f5b94c0
ffffffff3f5d2180
ffffffff3f5d24c0
ffffffff3f5eb180
ffffffff3f5eb4c0
fffffffe4683d480
ffffffff3657a480
 
 
위  주소 리스트는 struct worker_pool 구조체이며 다음과 같이 확인할 수 있습니다.
 
1: ffffffff3f53c4c0 struct worker_pool 디버깅
[0]: watchdog_ts = 4295299595 // 워커 터치 시간
[1]: 해당 워커풀에 큐잉한 워크
[2]: 실행 중인 워커: 0xFFFFFFFEE11F5B80
 
  (struct worker_pool *) (struct worker_pool*)0xffffffff3f53c180 = 0xFFFFFFFF3F53C180 = end+0x68B4978180 -> (
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((atomic_t) val = ((int) counter = 0), (u8) locked = 0, (u8)
    (int) cpu = 0,
    (int) node = 0,
    (int) id = 0,
    (unsigned int) flags = 0,
    (long unsigned int) watchdog_ts = 4295299595, "//<<--[0]"
    (struct list_head) worklist = (
      (struct list_head *) next = 0xFFFFFF9689B2D098 = binder_deferred_work.entry -> (  "//<<--[1]"
        (struct list_head *) next = 0xFFFFFF9689A052C8 = delayed_fput_work.work.entry, "//<<--[1]"
        (struct list_head *) prev = 0xFFFFFFFF3F53C1A0 = end+0x68B49781A0),
      (struct list_head *) prev = 0xFFFFFF9689B65710 = nd_tbl.gc_work.work.entry), "//<<--[1]"
    (int) nr_workers = 3,
    (int) nr_idle = 2,
    (struct list_head) idle_list = ((struct list_head *) next = 0xFFFFFFFEE4BF0680 = end+0x685A02C680, (struct list_head *) prev = 0xFFFFFFFEE07B
    (struct timer_list) idle_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDEAD000000000200 
    (struct timer_list) mayday_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDEAD000000000200 
    (struct hlist_head [64]) busy_hash = (
      [0] = ((struct hlist_node *) first = 0x0 = ),
...
      [45] = ((struct hlist_node *) first = 0x0 = ),
      [46] = ((struct hlist_node *) first = 0xFFFFFFFEE11F5B80 = end+0x6856631B80), "//<<--[2]"
      [47] = ((struct hlist_node *) first = 0x0 = ),
 ...
      [63] = ((struct hlist_node *) first = 0x0 = )),
    (struct worker *) manager = 0x0 = ,
    (struct list_head) workers = ((struct list_head *) next = 0xFFFFFFFEE07B9BC8 = end+0x6855BF5BC8, (struct list_head *) prev = 0xFFFFFFFEE11F5B
    (struct completion *) detach_completion = 0x0 = ,
    (struct ida) worker_ida = ((struct radix_tree_root) ida_rt = ((spinlock_t) xa_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lo
    (struct workqueue_attrs *) attrs = 0xFFFFFFFE46881C00 = end+0x67BBCBDC00,
    (struct hlist_node) hash_node = ((struct hlist_node *) next = 0x0 = , (struct hlist_node * *) pprev = 0x0 = ),
    (int) refcnt = 1,
    (atomic_t) nr_running = ((int) counter = 1),
    (struct callback_head) rcu = ((struct callback_head *) next = 0x0 = , (void (*)()) func = 0x0 = ))
 
리눅스 커널 디버깅 중 라딕스 트리를 만났을 때 이 명령어를 잘 활용합시다.
 
> Written-by: Austin Kim, 09/29/2019
> Posted-by: Austin Kim, 09/30/2019
>
 
Overview
 
많은 개발자들은 '브링업'이란 개발 업무를 맡고 있습니다. 이 과정에서 다음과 같은 어려움을 많이 겪습니다.
 
   * 보드 브링업을 하다 보니 컴퓨터의 시작 화면까지 보이지 않는다.
   * 부팅 과정에서 출력하는 화면이 계속 출력된다.
 
이런 이슈를 겪은 적이 있나요? 보통 이런 상황을 겪으면 굉장히 답답하고 짜증이 납니다. 그 이유는 무엇일까요?
 
   * 뭔가 문제가 생겨서 부팅이 안되는 상황인데 그 원인을 파악하기 어렵기 때문입니다. 
 
달리 말씀을 드리면 정확히 어디에 문제가 생겨서 부팅이 안되는지 알 수 없기 때문입니다. 이번 포스팅에서는 이런 상황에서 사용하면 아주 유용한 커널 패치를 소개합니다.
 
아이디어 
 
안드로이드나 라즈비안 타겟 부팅 과정에서 init이나 systemd 프로세스가 *.rc나 *.sh 셸 스크립트를 실행하면서 각종 서비스나 프로세스를 초기화합니다. 하지만 리눅스 커널 입장에서는 위 레이어에 안드로이드나 라즈비안 혹은 우분투가 돌아가는지 모릅니다. 이를 다음과 같이 요약해 볼까요?
 
    "유저 공간에서 프로세스나 스레드 생성 요청이 오면 이를 생성한다."
    "'실행 파일'을 실행할 때 커널은 말 그대로 실행시켜준다."
 
만약 안드로이드에서 각종 서비스를 실행할 때 커널은 이를 어떻게 처리할까요?
 
   * /system/bin/sh 파일 처럼 실행할 뿐입니다. 
 
커널 패치
 
서론이 길었는데, 먼저 커널 패치 코드를 소개합니다.
 
diff --git a/fs/exec.c b/fs/exec.c
index 77c03ce..49b51ce 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1681,6 +1681,37 @@ int search_binary_handler(struct linux_binprm *bprm)
 }
 EXPORT_SYMBOL(search_binary_handler);
 
+#define PROCESS_ARG_BUFF_SIZE 128
+
+void get_process_exec_profile(const char *fname)
+{
+       char process_arg_buf[PROCESS_ARG_BUFF_SIZE] = {0,};
+       int len = 0;
+
+       len = get_cmdline(current, process_arg_buf, PROCESS_ARG_BUFF_SIZE -1);
+
+    if(len == 0) {
+               pr_info("[-] Error: len: %d \n", len);
+               return;
+       }
+
+       if(!fname) {
+               pr_info("[-] Error: fname null \n");
+               return;
+       }
+
+       if (len > PROCESS_ARG_BUFF_SIZE)
+               len = PROCESS_ARG_BUFF_SIZE;
+
+       process_arg_buf[len] = '\0';
+
+       pr_info("[exec:%s-%d] exec_file: %s, arg: [%s] \n",
+                                       current->comm, current->pid, fname, process_arg_buf);
+}
+#endif
+
+
 static int exec_binprm(struct linux_binprm *bprm)
 {
        pid_t old_pid, old_vpid;
@@ -1698,6 +1729,9 @@ static int exec_binprm(struct linux_binprm *bprm)
                trace_sched_process_exec(current, old_pid, bprm);
                ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
                proc_exec_connector(current);
+               get_process_exec_profile(bprm->filename);
        }
 
        return ret;
 
패치 코드의 내용은 실행 파일과 실행 파일에 적용되는 아규먼트 옵션을 커널 로그로 출력해주는 것입니다.
 
커널 로그 확인
 
커밋을 반영하고 커널 로그를 받아보면 아래와 같은 시그니처를 볼 수 있습니다. 참고로 아래 로그는 안드로이드 디바이스에서 받았습니다.
 
2735:[   77.839144 / 01-01 06:09:53.099][6] [exec:init-1] exec_file: /init, arg: [/init]
2748:[   78.472964 / 01-01 06:09:53.739][7] [exec:init-1] exec_file: /system/bin/init, arg: [/system/bin/init]
2761:[   78.957614 / 01-01 06:09:54.229][7] [exec:init-1] exec_file: /system/bin/init, arg: [/system/bin/init]
2776:[   79.170175 / 01-01 06:09:54.429][1] [exec:init-542] exec_file: /system/bin/init, arg: [/system/bin/init]
2777:[   79.172863 / 01-01 06:09:54.439][3] [exec:init-543] exec_file: /system/bin/init, arg: [/system/bin/init]
2780:[   79.298571 / 01-01 06:09:54.559][1] [exec:ueventd-544] exec_file: /system/bin/ueventd, arg: [/system/bin/ueventd]
2781:[   79.317985 / 01-01 06:09:54.579][0] [exec:apexd-545] exec_file: /system/bin/apexd, arg: [/system/bin/apexd]
2804:[   79.584330 / 01-01 06:09:54.849][1] [exec:modprobe-546] exec_file: /vendor/bin/modprobe, arg: [/vendor/bin/modprobe]
2828:[   80.124861 / 01-01 06:09:55.389][2] [exec:modprobe-555] exec_file: /vendor/bin/modprobe, arg: [/vendor/bin/modprobe]
3518:[  385.662491 / 01-01 06:15:00.929][5] [exec:servicemanager-562] exec_file: /system/bin/servicemanager, arg: [/system/bin/servicemanager]
3519:[  385.675577 / 01-01 06:15:00.939][6] [exec:logd-560] exec_file: /system/bin/logd, arg: [/system/bin/logd]
3520:[  385.675634 / 01-01 06:15:00.939][7] [exec:hwservicemanage-563] exec_file: /system/bin/hwservicemanager, arg: [/system/bin/hwservicemanager]
3522:[  385.711358 / 01-01 06:15:00.979][5] [exec:vndservicemanag-564] exec_file: /vendor/bin/vndservicemanager, arg: [/vendor/bin/vndservicemanager]
3524:[  385.763532 / 01-01 06:15:01.029][6] [exec:android.hardwar-567] exec_file: /vendor/bin/hw/android.hardware.keymaster@4.0-service-qti, arg: [/vendor/bin/hw/android.hardware.keymaster@4.0-service-qti]
3525:[  385.764325 / 01-01 06:15:01.029][4] [exec:android.hardwar-569] exec_file: /vendor/bin/hw/android.hardware.keymaster@4.0-strongbox-service-qti, arg: [/vendor/bin/hw/android.hardware.keymaster@4.0-strongbo
x-service-qti]
3527:[  385.843622 / 01-01 06:15:01.109][6] [exec:vold-574] exec_file: /system/bin/vold, arg: [/system/bin/vold]
3532:[  385.929238 / 01-01 06:15:01.189][4] [exec:e2fsck-589] exec_file: /system/bin/e2fsck, arg: [/system/bin/e2fsck]
3547:[  386.199287 / 01-01 06:15:01.459][6] [exec:vdc-597] exec_file: /system/bin/vdc, arg: [/system/bin/vdc]
3557:[  386.342132 / 01-01 06:15:01.609][6] [exec:recovery-refres-598] exec_file: /system/bin/recovery-refresh, arg: [/system/bin/recovery-refresh]
3559:[  386.364102 / 01-01 06:15:01.629][4] [exec:spdaemon-599] exec_file: /vendor/bin/spdaemon, arg: [/vendor/bin/spdaemon]
3560:[  386.375250 / 01-01 06:15:01.639][4] [exec:sec_nvm-600] exec_file: /vendor/bin/sec_nvm, arg: [/vendor/bin/sec_nvm]
3563:[  386.412097 / 01-01 06:15:01.679][7] [exec:ipacm-diag-601] exec_file: /system/vendor/bin/ipacm-diag, arg: [/system/vendor/bin/ipacm-diag]
3565:[  386.414373 / 01-01 06:15:01.679][5] [exec:android.hardwar-603] exec_file: /vendor/bin/hw/android.hardware.atrace@1.0-service, arg: [/vendor/bin/hw/android.hardware.atrace@1.0-service]
3566:[  386.418336 / 01-01 06:15:01.679][5] [exec:ipacm-602] exec_file: /system/vendor/bin/ipacm, arg: [/system/vendor/bin/ipacm]
3567:[  386.436995 / 01-01 06:15:01.699][1] [exec:android.hardwar-604] exec_file: /vendor/bin/hw/android.hardware.boot@1.0-service, arg: [/vendor/bin/hw/android.hardware.boot@1.0-service]
3568:[  386.443772 / 01-01 06:15:01.709][5] [exec:wait_for_keymas-607] exec_file: /system/bin/wait_for_keymaster, arg: [/system/bin/wait_for_keymaster]
 
그렇다면 위 로그를 어떻게 해석할까요? 한 가지 예를 들겠습니다.
 
[  385.662491 / 01-01 06:15:00.929][5] [exec:servicemanager-562] exec_file: /system/bin/servicemanager, arg: [/system/bin/servicemanager] 
 
서비스 매니저가 실행됐다는 정보입니다. arg는 파일을 실행할 때의 아규먼트입니다.
이번에 다른 로그를 봅시다.
 
[  400.060155 / 01-01 06:15:14.839][4] [exec:init.rpi_device.crash-1144] exec_file: /vendor/bin/init.rpi_device.crashdata.sh, arg: [/vendor/bin/sh]
 
'/vendor/bin/init.rpi_device.crashdata.sh' 셸 스크립트를 init.rpi_device.crash.sh 스크립트가 실행합니다.
 
부팅하고 난 다음에 다음 시그니처도 확인할 수 있습니다. 
[  442.944003 / 07-30 05:19:25.069][3] [exec:crash_dump64-4141] exec_file: /system/bin/crash_dump64, arg: [crash_dump64]
 
유저 공간에서 Tombstone이 떨어졌다는 사실을 알 수 있습니다.
 
다음은 가장 흥미로운 로그입니다.
[  450.178083 / 07-30 05:19:32.299][2] [exec:sh-4946] exec_file: /system/bin/sh, arg: [/system/bin/sh]
[  450.224563 / 07-30 05:19:32.349][7] [exec:dmesg-4946] exec_file: /system/bin/dmesg, arg: [dmesg]
 
위 로그는 다음 명령어를 실행했다는 사실을 알려줍니다.
      adb shell dmesg > kernel_log.c
 
결론
 
이 패치를 적용하면 커널 로그를 통해 임베디드 리눅스의 어떤 배포판(안드로이드, 라즈비안)이던 부팅 과정을 확인할 수 있습니다. 리눅스 시스템 개발자들이여, 비슷한 문제를 겪으면 이 패치을 적용해 조금 더 일찍 퇴근합시다. 
 
패치 설명 동영상



+ Recent posts