임베디드 개발에서 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를 잘 활용해서 일찍 퇴근합시다.

+ Recent posts