<kernel v5.0>
Debug Objects
커널에서 사용하는 객체를 트래킹하기 위해 별도로 Debug Object를 할당하고 상태 값을 기록해두어 life time을 트래킹할 수 있도록 한다.
객체를 커널에서 할당하여 사용 시 종종 다음과 같은 실수를 반복한다.
- 사용중인 객체의 할당 해제
- 사용중인 객체의 재초기화
Debug Object를 사용하면 다음을 수행할 때마다 트래킹한다.
- 객체 초기화
- 객체 추가
- 객체 삭제
참고: infrastructure to debug (dynamic) objects
특징
- Debug Object 코드를 사용하려면 다음 커널 옵션을 설정해야 한다.
- CONFIG_DEBUG_KERNEL
- CONFIG_DEBUG_OBJECTS
- Debug Object 기능을 enable 하기 위해서는 부트업 타임에 다음을 준비하여야 한다.
- CONFIG_DEBUG_OBJECTS_ENABLE_DEFAULT 커널 옵션을 사용하거나
- “debug_objects=1” 커널 파라메터를 사용한다.
- 문제가 발생하여 로그를 출력할 때 최대 5번까지로 제한한다.
- /sys/kernel/debug/debug_objects 디렉토리에서 트래킹을 사용한다.
- 예) cat stats
적용된 커널 소스
- 타이머
- CONFIG_DEBUG_OBJECTS_TIMERS 커널 옵션 필요
- 워크큐
- CONFIG_DEBUG_OBJECTS_WORK 커널 옵션 필요
- RCU
- CONFIG_DEBUG_OBJECTS_RCU_HEAD 커널 옵션 필요
- per-cpu 카운터
- DEBUG_OBJECTS_PERCPU_COUNTER 커널 옵션 필요
- kfree() & vfree()
- CONFIG_DEBUG_OBJECTS_FREE 옵션 필요
- 오브젝트의 deactivation 과정을 감시할 수 있다. (leak 감시)
Debug Object 상태 전환
다음은 Debug Object의 상태들이다.
- none
- 객체를 할당 받지 않은 상태이다.
- Debug Object가 처음 할당되어 객체 풀에서 대기한다.
- init
- 객체를 할당 받은 초기 상태이다.
- Debug Object는 해시 리스트에서 관리된다.
- active
- 객체에 접근 가능한 상태이다.
- Debug Object는 해시 리스트에서 관리된다.
- inactive
- 객체에 접근을 허용하지 않은 상태이다.
- Debug Object는 해시 리스트에서 관리된다.
- destroyed
- 객체가 파괴된 상태이다.
- Debug Object는 해시 리스트에서 관리된다.
- notavailable
- 객체가 할당 해제된 상태이다.
- Debug Object는 재사용을 위해 객체 풀에서 대기한다.
Debug Object의 상태가 바뀌는 과정을 보여준다.
Debug Object 초기화
Debug Object를 사용하기 위해 커널은 다음과 같아 두 단계에 걸쳐 초기화를 수행된다.
- 부트업 초반에 debug_objects_early_init()를 통해 early 초기화
- 처음 슬랩을 사용하는 kmem 캐시를 사용하기 전까지 임시로 사용할 static debug object를 사용한다.
- 부트업 후반에 debug_objects_mem_init()을 통해 정규 초기화
- 슬랩을 사용하는 kmem 캐시가 준비된 후 기존 static debug object를 모두 kmem 캐시에서 할당한 객체로 migration 한다.
다음 그림은 debug object의 2 단계 초기화를 보여준다.
Debug Object 주요 API
Debug Object의 주요 API는 다음과 같다.
- debug_object_init()
- debug_object_init_on_stack()
- debug_object_activate()
- debug_object_deactivate()
- debug_object_destroy()
- debug_object_free()
Debug Object Life-time
Debug object의 할당은 아래 두 함수에서 요청되고 Debug Pool에서 할당해준다. Debug Pool의 Debug Object가 최소 레벨(default: 256) 보다 부족해지는 경우 kem 캐시를 통해 refill 한다.
- debug_object_init()
- debug_object_init_on_stack()
Debug Object의 할당 해제는 아래 함수에서 요청되고 Debug Pool로 이동시킨다. 만일 Debug Pool의 Debug Object가 pool size(defautl: 1024)를 초과하는 경우 할당 해제하여 kmem 캐시로 돌려보낸다.
- debug_object_free()
초기화
debug_objects_early_init()
lib/debugobjects.c
/* * Called during early boot to initialize the hash buckets and link * the static object pool objects into the poll list. After this call * the object tracker is fully operational. */
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); }
kmem 캐시가 준비가되기 직전의 커널 부트업에서 사용할 Debug Object 사용을 위해 초기화를 수행한다. 커널 빌드 시 준비해 둔 1024개의 static debug object를 임시로 사용한다.
- 코드 라인 5~6에서 Debug Object 해시를 초기화한다.
- ODEBUG_HASH_SIZE=16K
- 코드 라인 8~9에서 Debug Object 풀을 초기화한다.
- ODEBUG_POOL_SIZE=1024
- s390 아키텍처를 위해 512 -> 1024로 증가시켰다.
- ODEBUG_POOL_SIZE=1024
debug_objects_mem_init()
lib/debugobjects.c
/* * Called after the kmem_caches are functional to setup a dedicated * cache pool, which has the SLAB_DEBUG_OBJECTS flag set. This flag * prevents that the debug code is called on kmem_cache_free() for the * debug tracker objects to avoid recursive calls. */
void __init debug_objects_mem_init(void) { if (!debug_objects_enabled) return; obj_cache = kmem_cache_create("debug_objects_cache", sizeof (struct debug_obj), 0, SLAB_DEBUG_OBJECTS | SLAB_NOLEAKTRACE, NULL); if (!obj_cache || debug_objects_replace_static_objects()) { debug_objects_enabled = 0; kmem_cache_destroy(obj_cache); pr_warn("out of memory.\n"); } else debug_objects_selftest(); /* * Increase the thresholds for allocating and freeing objects * according to the number of possible CPUs available in the system. */ debug_objects_pool_size += num_possible_cpus() * 32; debug_objects_pool_min_level += num_possible_cpus() * 4; }
Debug Object 기능이 enable된 경우 사용을 위해 kmem 캐시등을 준비하고 기존 static debug object를 keme 캐시에서 할당받은 debug object로 migration 한다. 그 후 사용할 pool 사이즈와 최소 개수를 cpu 수에 맞게 적절히 조절한다.
- 코드 라인 3~4에서 Debug Object 기능을 enable 하지 않은 경우 함수를 빠져나간다.
- 코드 라인 6~9에서 debug_obj 구조체를 위해 kmem 캐시를 준비하고
- 코드 라인 11~16에서 기존 static debug object를 keme 캐시에서 할당받은 debug object로 migration 한다.
- 코드 라인 22에서 pool size를 possible cpu 개수 * 32 만큼 증가시킨다.
- 코드 라인 23에서 pool 최소 레벨도 poosible cpu 개수 * 4 만큼 증가시킨다.
주요 함수들
debug_object_init()
lib/debugobjects.c
/** * debug_object_init - debug checks when an object is initialized * @addr: address of the object * @descr: pointer to an object specific debug description structure */
void debug_object_init(void *addr, struct debug_obj_descr *descr) { if (!debug_objects_enabled) return; __debug_object_init(addr, descr, 0); } EXPORT_SYMBOL_GPL(debug_object_init);
객체를 초기화 시 디버그 체크를 수행한다.
/** * debug_object_init_on_stack - debug checks when an object on stack is * initialized * @addr: address of the object * @descr: pointer to an object specific debug description structure */
void debug_object_init_on_stack(void *addr, struct debug_obj_descr *descr) { if (!debug_objects_enabled) return; __debug_object_init(addr, descr, 1); } EXPORT_SYMBOL_GPL(debug_object_init_on_stack);
스택위에 있는 객체를 초기화 시 디버그 체크를 수행한다.
- 기존 상태가 active 및 destroyed에서 진입한 경우 에러 메시지를 출력한다.
__debug_object_init()
lib/debugobjects.c
static void __debug_object_init(void *addr, struct debug_obj_descr *descr, int onstack) { enum debug_obj_state state; struct debug_bucket *db; struct debug_obj *obj; unsigned long flags; fill_pool(); db = get_bucket((unsigned long) addr); raw_spin_lock_irqsave(&db->lock, flags); obj = lookup_object(addr, db); if (!obj) { obj = alloc_object(addr, db, descr); if (!obj) { debug_objects_enabled = 0; raw_spin_unlock_irqrestore(&db->lock, flags); debug_objects_oom(); return; } debug_object_is_on_stack(addr, onstack); } switch (obj->state) { case ODEBUG_STATE_NONE: case ODEBUG_STATE_INIT: case ODEBUG_STATE_INACTIVE: obj->state = ODEBUG_STATE_INIT; break; case ODEBUG_STATE_ACTIVE: debug_print_object(obj, "init"); state = obj->state; raw_spin_unlock_irqrestore(&db->lock, flags); debug_object_fixup(descr->fixup_init, addr, state); return; case ODEBUG_STATE_DESTROYED: debug_print_object(obj, "init"); break; default: break; } raw_spin_unlock_irqrestore(&db->lock, flags); }
객체를 초기화 시 디버그 체크를 수행한다.
- 코드 라인 8에서 object pool에 준비되어 있는 오브젝트가 min level 이하인 경우 추가 할당을 해둔다.
- 코드 라인 10에서 객체 주소의 pfn을 이용한 해시를 통해 debug bucket을 가져온다.
- 코드 라인 12~24에서 debug bucket에 락을 걸고 객체 주소에 해당하는 Debug Object를 검색한다. 만일 존재하지 않는 경우 Debug Object를 할당한다.
- 코드 라인 26~45에서 Debug Object의 상태가 active나 destroyed이면 문제가 발생하였으므로 에러 메시지를 출력하고, active 상태인 경우는 추가 fixup 코드를 수행한다.
- 예) ODEBUG: assert_init not available (active state 0) object type: timer_list hint: stub_timer+0x0/0x20
다음 그림은 debug_object_init()을 수행할 때 debug_object의 이동 또는 추가 할당되는 모습을 보여준다.
debug_object_activate()
lib/debugobjects.c
/** * debug_object_activate - debug checks when an object is activated * @addr: address of the object * @descr: pointer to an object specific debug description structure * Returns 0 for success, -EINVAL for check failed. */
int debug_object_activate(void *addr, 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) { switch (obj->state) { case ODEBUG_STATE_INIT: case ODEBUG_STATE_INACTIVE: obj->state = ODEBUG_STATE_ACTIVE; ret = 0; break; case ODEBUG_STATE_ACTIVE: debug_print_object(obj, "activate"); state = obj->state; raw_spin_unlock_irqrestore(&db->lock, flags); ret = debug_object_fixup(descr->fixup_activate, addr, state); return ret ? 0 : -EINVAL; case ODEBUG_STATE_DESTROYED: debug_print_object(obj, "activate"); ret = -EINVAL; break; default: ret = 0; break; } raw_spin_unlock_irqrestore(&db->lock, flags); 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. */ if (descr->is_static_object && descr->is_static_object(addr)) { /* track this static object */ 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; } EXPORT_SYMBOL_GPL(debug_object_activate);
객체를 활성화 시 디버그 체크를 수행한다.
- 코드 라인 12~13에서 Debug Object 기능을 enable 하지 않은 경우 함수를 빠져나간다.
- 코드 라인 15에서 객체 주소의 pfn을 이용한 해시를 통해 debug bucket을 가져온다.
- 코드 라인 17~19에서 debug bucket에 락을 걸고 객체 주소에 해당하는 Debug Object를 검색한다.
- 코드 라인 20~45에서 Debug Object의 상태가 이미 activate 이거나 destroyed 이면 문제가 발생하였으므로 에러 메시지를 출력하고, active 상태인 경우는 추가 fixup 코드를 수행한다.
- 코드 라인 55~64에서 Debug Object 검색이 안된 경우이다. Debug Object의 디스크립터가 static 객체인경우에만 Debug Object를 새로 초기화하고 activate상태로 변경하여 static 객체를 트래킹하게 한다. 그렇지 않고 dynamic 객체인 경우 에러 메시지를 출력하고 fixup 코드를 수행한다.
debug_object_deactivate()
lib/debugobjects.c
/** * debug_object_deactivate - debug checks when an object is deactivated * @addr: address of the object * @descr: pointer to an object specific debug description structure */
void debug_object_deactivate(void *addr, struct debug_obj_descr *descr) { struct debug_bucket *db; struct debug_obj *obj; unsigned long flags; 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) { case ODEBUG_STATE_INIT: case ODEBUG_STATE_INACTIVE: case ODEBUG_STATE_ACTIVE: if (!obj->astate) obj->state = ODEBUG_STATE_INACTIVE; else debug_print_object(obj, "deactivate"); break; case ODEBUG_STATE_DESTROYED: debug_print_object(obj, "deactivate"); break; default: break; } } else { struct debug_obj o = { .object = addr, .state = ODEBUG_STATE_NOTAVAILABLE, .descr = descr }; debug_print_object(&o, "deactivate"); } raw_spin_unlock_irqrestore(&db->lock, flags); } EXPORT_SYMBOL_GPL(debug_object_deactivate);
객체를 비활성화 시 디버그 체크를 수행한다.
- 코드 라인 7~8에서 Debug Object 기능을 enable 하지 않은 경우 함수를 빠져나간다.
- 코드 라인 10에서 객체 주소의 pfn을 이용한 해시를 통해 debug bucket을 가져온다.
- 코드 라인 12~14에서 debug bucket에 락을 걸고 객체 주소에 해당하는 Debug Object를 검색한다.
- 코드 라인 15~31에서 Debug Object의 상태가 실제 activate 된 적이 없는 모든 경우에 대해 문제가 발생하였으므로 에러 메시지를 출력한다.
- 코드 라인 32~38에서 Debug Object 검색이 안된 경우이다. 이 경우에도 에러 메시지를 출력한다.
debug_object_destroy()
lib/debugobjects.c
/** * debug_object_destroy - debug checks when an object is destroyed * @addr: address of the object * @descr: pointer to an object specific debug description structure */
void debug_object_destroy(void *addr, struct debug_obj_descr *descr) { enum debug_obj_state state; struct debug_bucket *db; struct debug_obj *obj; unsigned long flags; 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_NONE: case ODEBUG_STATE_INIT: case ODEBUG_STATE_INACTIVE: obj->state = ODEBUG_STATE_DESTROYED; break; case ODEBUG_STATE_ACTIVE: debug_print_object(obj, "destroy"); state = obj->state; raw_spin_unlock_irqrestore(&db->lock, flags); debug_object_fixup(descr->fixup_destroy, addr, state); return; case ODEBUG_STATE_DESTROYED: debug_print_object(obj, "destroy"); break; default: break; } out_unlock: raw_spin_unlock_irqrestore(&db->lock, flags); } EXPORT_SYMBOL_GPL(debug_object_destroy);
객체를 소멸 시 디버그 체크를 수행한다.
- 코드 라인 8~9에서 Debug Object 기능을 enable 하지 않은 경우 함수를 빠져나간다.
- 코드 라인 11에서 객체 주소의 pfn을 이용한 해시를 통해 debug bucket을 가져온다.
- 코드 라인 13~17에서 debug bucket에 락을 걸고 객체 주소에 해당하는 Debug Object를 검색한다.
- 코드 라인 19~37에서 Debug Object의 상태가 active 또는 destroyed 상태인 경우 문제가 발생하였으므로 에러 메시지를 출력하고, active 상태인 경우 fixup 코드도 수행한다.
debug_object_free()
lib/debugobjects.c
/** * debug_object_free - debug checks when an object is freed * @addr: address of the object * @descr: pointer to an object specific debug description structure */
void debug_object_free(void *addr, struct debug_obj_descr *descr) { enum debug_obj_state state; struct debug_bucket *db; struct debug_obj *obj; unsigned long flags; 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: debug_print_object(obj, "free"); state = obj->state; raw_spin_unlock_irqrestore(&db->lock, flags); debug_object_fixup(descr->fixup_free, addr, state); return; default: hlist_del(&obj->node); raw_spin_unlock_irqrestore(&db->lock, flags); free_object(obj); return; } out_unlock: raw_spin_unlock_irqrestore(&db->lock, flags); } EXPORT_SYMBOL_GPL(debug_object_free);
객체의 할당 해제 시 디버그 체크를 수행한다.
- 코드 라인 8~9에서 Debug Object 기능을 enable 하지 않은 경우 함수를 빠져나간다.
- 코드 라인 11에서 객체 주소의 pfn을 이용한 해시를 통해 debug bucket을 가져온다.
- 코드 라인 13~17에서 debug bucket에 락을 걸고 객체 주소에 해당하는 Debug Object를 검색한다.
- 코드 라인 19~37에서 Debug Object의 상태가 active 상태인 경우 문제가 발생하였으므로 에러 메시지를 출력하고, fixup 코드도 수행한다.
다음 그림은 debug_object_free() 함수 호출 시 Debug object가 할당 해제되어 객체 풀로 되돌아가거나 kmem 캐시로 회수되는 모습을 보여준다.
Fixup Operations
아래 API들을 사용할 때 이미 Debug Object가 active 상태인 경우 에러가 발생하고, 디스크립터에 구현된 fixup 후크 함수를 호출한다.
- debug_object_init() -> active 상태를 만나면 fixup 후크 함수를 호출한다.
- debug_object_activate() -> active 상태를 만나면 fixup 후크 함수를 호출한다.
- debug_object_deactivate() -> 없음
- debug_object_destroy() -> active 상태를 만나면 fixup 후크 함수를 호출한다.
- debug_object_free() -> active 상태를 만나면 fixup 후크 함수를 호출한다.
다음 그림은 Debug Object를 deactivate 상태 변환 없이 destroy 상태로 변경할 때 fixup 후크 함수가 호출되는 모습을 보여준다.
구조체
debug_object 구조체
include/linux/debugobjects.h
/** * struct debug_obj - representaion of an tracked object * @node: hlist node to link the object into the tracker list * @state: tracked object state * @astate: current active state * @object: pointer to the real object * @descr: pointer to an object type specific debug description structure */
struct debug_obj { struct hlist_node node; enum debug_obj_state state; unsigned int astate; void *object; struct debug_obj_descr *descr; };
- node
- 객체 pool이나 해시리스트에 연결될 때 사용되는 노드이다.
- state
- 트래킹할 객체 상태 값이다.
- astate
- 현재 active 상태 값을 나타낸다. (초기화 시 0)
- debug_object_active_state() API를 통해서 설정되며, 현재는 특정 gpu 드라이버에서만 사용되고 있다.
- object
- 실제 객체 주소
- descr
- debug object descriptor를 가리킨다.
debug_obj_descr 구조체
include/linux/debugobjects.h
/** * struct debug_obj_descr - object type specific debug description structure * * @name: name of the object typee * @debug_hint: function returning address, which have associated * kernel symbol, to allow identify the object * @is_static_object: return true if the obj is static, otherwise return false * @fixup_init: fixup function, which is called when the init check * fails. All fixup functions must return true if fixup * was successful, otherwise return false * @fixup_activate: fixup function, which is called when the activate check * fails * @fixup_destroy: fixup function, which is called when the destroy check * fails * @fixup_free: fixup function, which is called when the free check * fails * @fixup_assert_init: fixup function, which is called when the assert_init * check fails */
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); };
Debug Object 디스크립터에는 이름과 몇 개의 fixup용 후크 함수들이 지정된다.
- *name
- 디스크립터를 설명하고 보여줄 수 있는 이름
- (*is_static_object)
- 객체가 static 상태인지 여부를 판단할 수 있는 후크 함수와 연결된다.
- 예) timer_is_static_object(), work_is_static_object(), rcuhead_is_static_object()
- 객체가 static 상태인지 여부를 판단할 수 있는 후크 함수와 연결된다.
- (*debug_hint)
- 에러 메시지를 출력할 때 사용 위치를 보여주는 기능이다.
- 사용 위치가 커널에 심볼로 export 되어있는 함수의 경우 다음 사용예와 같이 함수 명과 함수에서의 Debug Object를 사용한 상대 위치가 표시된다.
- 예) ODEBUG: free active (active state 0) object type: timer_list hint: process_timeout+0x0/0x10
debug_bucket 구조체
dll/debugobjects.c
struct debug_bucket { struct hlist_head list; raw_spinlock_t lock; }; static struct debug_bucket obj_hash[ODEBUG_HASH_SIZE];
- list
- 사용 중 상태의 Debug Object가 연결될 리스트이다.
- lock
- 위의 list 추가 삭제 시 필요한 lock이다.
obj_hash[] 배열
dll/debugobjects.c
static struct debug_bucket obj_hash[ODEBUG_HASH_SIZE];
사용 중 상태의 Debug Object가 모여 있는 해시리스트이다.
obj_static_pool[] 배열
dll/debugobjects.c
static struct debug_obj obj_static_pool[ODEBUG_POOL_SIZE] __initdata;
부트 업 과정 중 kmem 캐시가 활성화 되기 전까지 Debug Object를 사용해야 하는 경우 static pool을 활용한다.
- 부트업이 완료되면 사용하지 않는다.
참고
- Kernel Debugging | 문c
- The object-lifetime debugging infrastructure | Kernel.org