임베디드 개발에서 T32-Trace32 장비를 많이 써서 소스 코드 디버깅을 합니다.
이번 시간에는 리눅스 커널에서 웨이크락(wake lock)을 잡는 모듈이 누구인지 파악하는 디버깅(Debugging) 방법을 소개합니다.
리눅스 커널는 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)))
리눅스 커널 소스 코드에서 구현된 그래도 매크로를 선언하는 것입니다.
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
((type *)(__mptr - offsetof(type, member))); })
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
우선 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)
wakelock을 잡은 모듈들은 wakeup_sources 이란 전역 변수에 등록돼 있습니다.
계속 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를 잘 활용해서 일찍 퇴근합시다.
'Kernel Crash Case-Studies' 카테고리의 다른 글
[KernelCrash] Abort at 0xecb29f00(defective device) (0) | 2019.03.09 |
---|---|
[KernelCrash] Abort at mmc_wait_data_done() due to race (0) | 2019.03.09 |
[KernelCrash] Abort at __stack_chk_fail() due to defective memory (0) | 2019.03.09 |
[KernelCrash] Abort at rmqueue_bulk() due to page.lru->next corruption (0) | 2019.03.09 |
[리눅스커널][크래시분석] 뮤텍스 데드락(Mutex Deadlock) 락업(lockup) - "simpleperf" 디버깅 (0) | 2019.02.20 |