Linux Tutorial #15 debug-objects
이번 장에서 설명할 내용은 debugobjects
이다. Documentation/core-api/debug-object.rst
에 자세하게 기술되어 있는데 이들의 내용을 간단하게 요약하여 설명토록 하겠다.
공식 문서에 따르면 debugobjects
를 아래와 같이 기술한다.
debugobjects
는 커널 객체(object
)의 수명과 연산의 유효성을 추적하는 기반 시설이다.
debugobjects
는 다음과 같은 오류 패턴을 확인하는데 유용하다:
- 초기화되지 않은 객체의 활성화
- 활성화 객체의 초기화
- 해제된, 파괴된 객체의 사용
이번 장에서는 어떤 구조로 debugobjects
가 커널 객체의 유효성을 판단하는지 분석하는데 그 의의가 있다. 코드가 그렇게 길지 않으므로 천천히 하나 하나 뜯어보겠다. 해당 글에서 나오는 모든 코드는 include/linux/debugobjects.h
, lib/debugobjects.c
에서 확인할 수 있다.
1. 핵심 자료 구조
debugobjects
의 축이 되는 핵심 자료구조에 대해 먼저 소개하고, 이를 통해 어떤 방식으로 데이터의 유효성을 검사하는지 알아보겠다.
struct debug_obj
위 구조체는 추적되는 객체의 정보를 표현한다.
struct debug_obj {
struct hlist_node node;
enum debug_obj_state state;
unsigned int astate;
void *object;
const struct debug_obj_descr *descr;
};
node
: 추적 리스트에 객체를 연결하기 위한 hlist node
state
: 추적되는 객체의 상태
astate
: 현재 활성화 상태
object
: 실제 객체를 가르키는 포인터
descr
: 디버그 정보 구조체를 가르키는 포인터.
struct debug_obj_descr
위 구조체는 객체의 디버그 정보를 명세한다.
struct debug_obj_descr {
const char *name;
void *(*debug_hint)(void *addr);
bool (*is_static_object)(void *addr);
bool (*fixup_init)(void *addr, enum debug_obj_state state);
bool (*fixup_activate)(void *addr, enum debug_obj_state state);
bool (*fixup_destroy)(void *addr, enum debug_obj_state state);
bool (*fixup_free)(void *addr, enum debug_obj_state state);
bool (*fixup_assert_init)(void *addr, enum debug_obj_state state);
};
name
: 객체 자료형의 이름
debug_hint
: 객체를 나타내는 것을 허용하며, 커널 심볼과 연관된 주소를 반환하는 함수.
is_static_object
: obj
가 정적이라면 참을, 그렇지 않다면 거짓을 반환하는 함수.
fiexup_init
: 초기화 검사에 실패했을 때 호출되는 수리 함수. 모든 수리 함수는 수리에 성공했을 때 반드시 참을, 그렇지 않을 때 거짓을 반환해야 한다.
fixup_activate
: 활성화 검사에 실패했을 때 호출되는 수리 함수.
fixup_destroy
: 파괴 검사에 실패했을 때 호출되는 수리 함수.
fixup_free
: 해제 검사에 실패했을 때 호출되는 수리 함수.
fixup_assert_init
: assert_init
확인에 실패했을 때 호출되는 함수.
struct debug_bucket
추적되는 객체들은 hash
테이블에 저장되며, chaining hash
방식으로 각각의 객체가 연결된다.
struct debug_bucket {
struct hlist_head list;
raw_spinlock_t lock;
};
최종적으로 위와 같은 구조를 가지게 된다.
obj_hash
그리고 obj_static_pool
#define ODEBUG_HASH_BITS 14
#define ODEBUG_HASH_SIZE (1 << ODEBUG_HASH_BITS)
#define ODEBUG_POOL_SIZE 1024
static struct debug_bucket obj_hash[ODEBUG_HASH_SIZE];
static struct debug_obj obj_static_pool[ODEBUG_POOL_SIZE] __initdata;
static HLIST_HEAD(obj_pool);
static HLIST_HEAD(obj_to_free);
앞서 본 hash
테이블과 debug_obj
는 정적인 크기를 가지는 배열로 선언한다. 이후 obj_pool
과 obj_to_free
리스트를 통해 이들을 연결하여 위에서 본 그림과 같은 자료구조를 만들어 낸다.
자료구조만 봐서는 이들이 어떻게 사용될지 감이 안 잡힐 수 있다. 맨 위에서 말했듯이 debugobjects
는 커널 객체의 수명과 연산의 유효성을 검사하는 기반 시설이다. 어떻게 이들이 커널의 자료 구조를 보호할 수 있는지를 환기하고 2장
을 읽기 바란다.
2. 핵심 함수들
이제 위에서 설명한 자료구조들을 초기화하고 활용하는 함수들을 소개하겠다. 실행되는 순서대로 어떻게 돌아가는지 잘 살펴보길 바란다.
debug_object_early_init
함수
void __init debug_objects_early_init(void)
{
int i;
for (i = 0; i < ODEBUG_HASH_SIZE; i++)
raw_spin_lock_init(&obj_hash[i].lock);
for (i = 0; i < ODEBUG_POOL_SIZE; i++)
hlist_add_head(&obj_static_pool[i].node, &obj_pool);
}
debug_object_early_init
함수는 1번
에서 설명했던 hash table
과 object pool
을 초기화하는 함수이다.
debug_object_init
함수
debug_object_init
객체가 초기화될 때 그 유효성을 검사하는 함수이다. 커널 객체를 초기화할때 인자로 전달한 주소의 객체를 초기화해도 되는지, 혹은 안되는지를 알린다. 함수는 앞에 붙어 있는 언더바로 눈치챘을 수 있겠지만 래핑 함수(Wrapping Function
)이다. 실제 함수 원형은 이후에 설명하겠다.
static void
__debug_object_init(void *addr, const struct debug_obj_descr *descr, int onstack)
{
enum debug_obj_state state;
bool check_stack = false;
// debug_bucket 가르키는 구조체 포인터
struct debug_bucket *db;
// debug_obj 를 가르키는 구조체 포인터
struct debug_obj *obj;
unsigned long flags;
fill_pool();
// 주소를 해싱하여 bucket 을 가져온다.
db = get_bucket((unsigned long) addr);
// 락을 건다.
raw_spin_lock_irqsave(&db->lock, flags);
// bucket 에서 addr 에 해당하는 obj 를 찾는다.
obj = lookup_object(addr, db);
if (!obj) {
// 못 찾았다면 새롭게 할당하여 db 에 addr 을 연결
obj = alloc_object(addr, db, descr);
if (!obj) {
// 할당에 실패했다면, `debugobjects` 를 비활성화 시키고
debug_objects_enabled = 0;
// 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
// Out-of-memory 에러를 띄우고 반환한다.
debug_objects_oom();
return;
}
// 스택 검사를 참으로 설정
check_stack = true;
}
// 상태 검사
switch (obj->state) {
// NONE, INIT, INACTIVATE 라면 정상적으로 INIT
case ODEBUG_STATE_NONE:
case ODEBUG_STATE_INIT:
case ODEBUG_STATE_INACTIVE:
obj->state = ODEBUG_STATE_INIT;
break;
// 활성화된 객체의 초기화는 맨 위에서 말한 두 번째 조건에 위배된다.
case ODEBUG_STATE_ACTIVE:
state = obj->state;
// 스핀 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
// 경고 메세지를 띄우고
debug_print_object(obj, "init");
// init 에 대한 수리 함수를 호출한다.
debug_object_fixup(descr->fixup_init, addr, state);
return;
// 파괴된 객체를 초기화(사용) 하는 것은 세 번째 조건에 위배된다.
case ODEBUG_STATE_DESTROYED:
// 스핀 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
// 경고 메세지를 출력한다.
debug_print_object(obj, "init");
return;
default:
break;
}
// 스핀 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
// 앞선 check_stack 이 참이라면
if (check_stack)
// debug_obj 의 addr 이 커널 스택에 있는지 점검
debug_object_is_on_stack(addr, onstack);
}
핵심 라인에 주석을 달아 그 내용을 설명하였다. 앞선 세 가지 조건에 위배되는 상태에 대해서는 에러를 출력하는 것에 주목하라.
실제 함수 원형은 아래와 같다:
void debug_object_init(void *addr, const struct debug_obj_descr *descr)
{
if (!debug_objects_enabled)
return;
__debug_object_init(addr, descr, 0);
}
debug_objects
가 사용 불가일 땐 즉시 반환하고 그렇지 않다면 초기화에 대한 디버깅을 수행하는 __debug_object_init
함수를 호출한다.
debug_object_free
함수
debug_object_free
함수는 커널 객체의 해제의 유효성을 검사하는 함수이다. 코드를 보며 그 내용을 살피겠다:
void debug_object_free(void *addr, const struct debug_obj_descr *descr)
{
enum debug_obj_state state;
struct debug_bucket *db;
struct debug_obj *obj;
unsigned long flags;
// debugobjects 가 비활성화 상태라면 즉시 반환한다.
if (!debug_objects_enabled)
return;
db = get_bucket((unsigned long) addr);
raw_spin_lock_irqsave(&db->lock, flags);
obj = lookup_object(addr, db);
if (!obj)
goto out_unlock;
// 상태를 검사한다.
switch (obj->state) {
// 활성화 상태라면?
case ODEBUG_STATE_ACTIVE:
state = obj->state;
raw_spin_unlock_irqrestore(&db->lock, flags);
// 경고를 출력하고
debug_print_object(obj, "free");
// 수리 함수를 호출한다.
debug_object_fixup(descr->fixup_free, addr, state);
return;
// 그 외에는
default:
// chaning list 에서 obj 를 빼내고
hlist_del(&obj->node);
// 스핀 락을 푼 다음
raw_spin_unlock_irqrestore(&db->lock, flags);
// obj 를 해제한다.
free_object(obj);
return;
}
out_unlock:
raw_spin_unlock_irqrestore(&db->lock, flags);
}
debug_object_activate
함수
debug_object_activate
함수는 커널 객체의 활성화를 디버깅하는 함수이다. 자세한 설명은 역시 주석으로 달아 두겠다.
int debug_object_activate(void *addr, const struct debug_obj_descr *descr)
{
enum debug_obj_state state;
struct debug_bucket *db;
struct debug_obj *obj;
unsigned long flags;
int ret;
struct debug_obj o = { .object = addr,
.state = ODEBUG_STATE_NOTAVAILABLE,
.descr = descr };
if (!debug_objects_enabled)
return 0;
db = get_bucket((unsigned long) addr);
raw_spin_lock_irqsave(&db->lock, flags);
obj = lookup_object(addr, db);
if (obj) {
bool print_object = false;
// 상태 확인
switch (obj->state) {
// INIT 혹은 INACTIVE 라면
case ODEBUG_STATE_INIT:
case ODEBUG_STATE_INACTIVE:
// 상태를 변경하고
obj->state = ODEBUG_STATE_ACTIVE;
// 반환 값으로 사용 될 ret 을 0 으로 초기화한다.
ret = 0;
break;
// 상태가 ACTIVE 라면
case ODEBUG_STATE_ACTIVE:
state = obj->state;
raw_spin_unlock_irqrestore(&db->lock, flags);
// 에러 메세지를 출력하고
debug_print_object(obj, "activate");
// 수리 함수를 호출하고 그 반환값으로 ret 을 초기화한다.
ret = debug_object_fixup(descr->fixup_activate, addr, state);
return ret ? 0 : -EINVAL;
// 파괴된 상태라면?
case ODEBUG_STATE_DESTROYED:
// print_object 를 참으로 변경하고
print_object = true;
// 반환값으로 사용될 ret 을 -EINVAL 로 초기화.
ret = -EINVAL;
break;
// 그 외에는
default:
// ret 을 0 으로 초기화한다.
ret = 0;
break;
}
raw_spin_unlock_irqrestore(&db->lock, flags);
if (print_object)
debug_print_object(obj, "activate");
return ret;
}
// 스핀 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
/*
* We are here when a static object is activated. We
* let the type specific code confirm whether this is
* true or not. if true, we just make sure that the
* static object is tracked in the object tracker. If
* not, this must be a bug, so we try to fix it up.
*/
// 등록된 정적 함수가 있다면 addr 을 전달하여 호출
if (descr->is_static_object && descr->is_static_object(addr)) {
/* track this static object */
// addr 을 초기화하고
debug_object_init(addr, descr);
// 상태를 활성화시킨다.
debug_object_activate(addr, descr);
} else {
// 에러 메세지를 출력하고
debug_print_object(&o, "activate");
// 수리 함수를 호출한다.
ret = debug_object_fixup(descr->fixup_activate, addr,
ODEBUG_STATE_NOTAVAILABLE);
return ret ? 0 : -EINVAL;
}
return 0;
}
debug_object_deactivate
함수
위 함수는 커널 객체를 비활성화 할 때의 유효성을 검사한다. 자세한 내용 설명은 주석으로 남긴다.
void debug_object_deactivate(void *addr, const struct debug_obj_descr *descr)
{
struct debug_bucket *db;
struct debug_obj *obj;
unsigned long flags;
bool print_object = false;
if (!debug_objects_enabled)
return;
db = get_bucket((unsigned long) addr);
raw_spin_lock_irqsave(&db->lock, flags);
obj = lookup_object(addr, db);
if (obj) {
// 상태 확인
switch (obj->state) {
// INIT, INACTIVE, ACTIVE 상태라면
case ODEBUG_STATE_INIT:
case ODEBUG_STATE_INACTIVE:
case ODEBUG_STATE_ACTIVE:
// 현재 활성 상태를 확인
if (!obj->astate)
// 비활성화라면 객체의 상태를 비활성화로
obj->state = ODEBUG_STATE_INACTIVE;
else
// 객체 출력을 활성화
print_object = true;
break;
// 객체가 파괴되었다면
case ODEBUG_STATE_DESTROYED:
// 객체 출력을 활성화
print_object = true;
break;
default:
break;
}
}
// 스핀 락을 풀고
raw_spin_unlock_irqrestore(&db->lock, flags);
// 오브젝트가 존재하지 않는다면
if (!obj) {
// 사용 불가능 객체를 만들어서
struct debug_obj o = { .object = addr,
.state = ODEBUG_STATE_NOTAVAILABLE,
.descr = descr };
// 경고 메세지를 출력한다.
debug_print_object(&o, "deactivate");
} else if (print_object) {
// 경고 메세지를 출력한다.
debug_print_object(obj, "deactivate");
}
}
3. debugobjects
의 활용
리눅스 커널에서 debug objects
을 활용ㅇ하고 있는 소스 파일은 생각보다 많지 않다. 여기에서는 kernel/workqueue.c
에서 debugobjects
를 어떻게 활용하는지 살펴 보도록 하겠다.
// 디버그 힌트 콜백 함수
static void *work_debug_hint(void *addr)
{
return ((struct work_struct *) addr)->func;
}
// 정적 객체인지 판단하는 함수
static bool work_is_static_object(void *addr)
{
struct work_struct *work = addr;
return test_bit(WORK_STRUCT_STATIC_BIT, work_data_bits(work));
}
// 초기화 디버그 실패 시 호출되는 수리 함수
static bool work_fixup_init(void *addr, enum debug_obj_state state)
{
struct work_struct *work = addr;
switch (state) {
case ODEBUG_STATE_ACTIVE:
cancel_work_sync(work);
debug_object_init(work, &work_debug_descr);
return true;
default:
return false;
}
}
// 해제 디버그 실패 시 호출되는 수리 함수
static bool work_fixup_free(void *addr, enum debug_obj_state state)
{
struct work_struct *work = addr;
switch (state) {
case ODEBUG_STATE_ACTIVE:
cancel_work_sync(work);
debug_object_free(work, &work_debug_descr);
return true;
default:
return false;
}
}
// 위에서 설명한 내용을 등록하는 `struct debug_obj_descr` 구조체
static const struct debug_obj_descr work_debug_descr = {
.name = "work_struct", // debug_object 에 명칭 부여
.debug_hint = work_debug_hint, // 디버그 힌트 콜백 함수
.is_static_object = work_is_static_object, // 정적 객체인지 판단하는 함수
.fixup_init = work_fixup_init, // 디버그 실패 시 호출되는 수리 함수
.fixup_free = work_fixup_free, // 디버그 실패 시 호출되는 수리 함수
};
위 내용은 struct debug_obj_descr
구조체를 초기화하기 위해 선언한 함수 각종 함수이다. 위 내용을 바탕으로 어떻게 코드를 디버깅하는지 살펴 보자.
static inline void debug_work_activate(struct work_struct *work)
{
debug_object_activate(work, &work_debug_descr);
}
static inline void debug_work_deactivate(struct work_struct *work)
{
debug_object_deactivate(work, &work_debug_descr);
}
void __init_work(struct work_struct *work, int onstack)
{
if (onstack)
debug_object_init_on_stack(work, &work_debug_descr);
else
debug_object_init(work, &work_debug_descr);
}
EXPORT_SYMBOL_GPL(__init_work);
void destroy_work_on_stack(struct work_struct *work)
{
debug_object_free(work, &work_debug_descr);
}
EXPORT_SYMBOL_GPL(destroy_work_on_stack);
void destroy_delayed_work_on_stack(struct delayed_work *work)
{
destroy_timer_on_stack(&work->timer);
debug_object_free(&work->work, &work_debug_descr);
}
EXPORT_SYMBOL_GPL(destroy_delayed_work_on_stack);
kernel/workqueue.c
는 struct work_struct *work
구조체를 debug_object
로 등록한다. 해당 객체에 대해 debug_work_activate()
함수와 debug_work_deactivate()
함수를 호출하며 struct work_struct *work
가 올바르게 동작하는지 점검한다.
출처
[책] 리눅스 커널 소스 해설: 기초 입문 (정재준 저)
[사이트] https://www.kernel.org/doc/Documentation/core-api/debug-objects.rst
[사진] https://liujunming.top/2018/03/12/linux-kernel%E4%B8%ADHList%E5%92%8CHashtable/
Author And Source
이 문제에 관하여(Linux Tutorial #15 debug-objects), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mythos/Linux-Tutorial-15-debug-objects저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)