본문 바로가기

[Debugging] Tips

[crash-utility] wakelock 디버깅(rbtree: wakelocks_tree)

 
이번에 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를 추가합니다.
 
이 코드를 제대로 이해해야 크래시 유틸리티로 디버깅을 할 수 있습니다.
 
리눅스 커널 디버깅 능력은 디버깅 툴 명령어만 외운다고 얻을 수는 없습니다.
이렇게 코드 흐름과 자료 구조를 정확히 알아야 합니다.
 
디버깅을 잘하는 개발자한테 디버깅 툴만 잘 쓴다고 개소리하며 뇌까리는 무식한 관리자들을 보면 이럴 때 이 블로그 글을 보여주세요. 아니면 그냥 무시하던가요.