LKDTM 소개
LKDTM은 Linux Kernel Dump Test Module (LKDTM)의 약자로 kdump를 활용해 덤프(vmcore)가 제대로 추출되는지 테스트하는 용도로 작성된 드라이버입니다.
관련 자료는 아래 링크에서 확인할 수 있습니다.
LKDTM 소스는 커널 버전이 업그레이드되면서 (불필요하게) 복잡해 졌는데요.
전체 구조는 예전 v3.18 소스를 보면 더 빨리 파악할 수 있습니다.
LKDTM 컨피그를 키는 방법
LKDTM는 드라이버 모듈 형식으로 빌드하거나 아예 빌트인으로 커널 이미지에 포함되도록 빌드할 수 있습니다. 이번에는 커널 이미지에 LKDTM을 포함시키는 방법을 소개합니다.
아래는 LKDTM 컨피그를 키는 예제 코드입니다. 라즈비안 커널 소스 기준입니다.
diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig
index 16d756c..191fb28 100644
--- a/arch/arm64/configs/bcm2711_defconfig
+++ b/arch/arm64/configs/bcm2711_defconfig
@@ -16,6 +16,11 @@ CONFIG_IKCONFIG=m
CONFIG_IKCONFIG_PROC=y
CONFIG_MEMCG=y
CONFIG_BLK_CGROUP=y
CONFIG_DEBUG_INFO=y
+CONFIG_LKDTM=y
CONFIG_CFS_BANDWIDTH=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_FREEZER=y
bcm2711_defconfig 파일에 'CONFIG_LKDTM=y' 코드 한 줄이 추가됐음을 확인할 수 있습니다.
위와 같은 커널 컨피그를 적용한 후 빌드를 하면, .config(빌드하면 생성되는)에서 'CONFIG_LKDTM=y'가 확인됩니다.
$ vi out/.config
...
CONFIG_FUNCTION_ERROR_INJECTION=y
# CONFIG_FAULT_INJECTION is not set
CONFIG_ARCH_HAS_KCOV=y
CONFIG_RUNTIME_TESTING_MENU=y
CONFIG_LKDTM=y
# CONFIG_TEST_MIN_HEAP is not set
'CONFIG_LKDTM=y'을 반영하고 커널을 빌드합니다. 빌드한 다음에 생성되는 커널 이미지를 타겟에 다운로드(플래시)합니다.
LKDTM 실행 하기 전 체크 포인트
LKDTM 컨피그가 플레시된 타겟 디바이스에서, LKDTM 실행 하기 전에 먼저 아래 파일이 제대로 보이는지 확인해야 합니다.
/sys/kernel/debug/provoke-crash
만약 위 파일이 보이지 않으면 'CONFIG_LKDTM=y' 컨피그에 제대로 적용되지 않고 빌드됐으니 다시 빌드를 해야 합니다.
LKDTM 간략한 코드 리뷰
LKDTM을 실행하는 방법을 설명드리기 전에 간단히 LKDTM에 대한 코드를 리뷰합니다.
LKDTM은 강제로 크래시를 유발할 수 있는 코드로 구성돼 있으며 각각 종류는 아래 코드에서 확인할 수 있습니다. (v5.4 기준)
/* Define the possible types of crashes that can be triggered. */
static const struct crashtype crashtypes[] = {
CRASHTYPE(PANIC),
CRASHTYPE(BUG),
CRASHTYPE(WARNING),
CRASHTYPE(WARNING_MESSAGE),
CRASHTYPE(EXCEPTION),
CRASHTYPE(LOOP),
CRASHTYPE(EXHAUST_STACK),
CRASHTYPE(CORRUPT_STACK),
CRASHTYPE(CORRUPT_STACK_STRONG),
CRASHTYPE(CORRUPT_LIST_ADD),
CRASHTYPE(CORRUPT_LIST_DEL),
CRASHTYPE(CORRUPT_USER_DS),
CRASHTYPE(STACK_GUARD_PAGE_LEADING),
CRASHTYPE(STACK_GUARD_PAGE_TRAILING),
CRASHTYPE(UNSET_SMEP),
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
CRASHTYPE(OVERWRITE_ALLOCATION),
CRASHTYPE(WRITE_AFTER_FREE),
CRASHTYPE(READ_AFTER_FREE),
CRASHTYPE(WRITE_BUDDY_AFTER_FREE),
CRASHTYPE(READ_BUDDY_AFTER_FREE),
CRASHTYPE(SLAB_FREE_DOUBLE),
CRASHTYPE(SLAB_FREE_CROSS),
CRASHTYPE(SLAB_FREE_PAGE),
CRASHTYPE(SOFTLOCKUP),
CRASHTYPE(HARDLOCKUP),
CRASHTYPE(SPINLOCKUP),
CRASHTYPE(HUNG_TASK),
CRASHTYPE(EXEC_DATA),
CRASHTYPE(EXEC_STACK),
CRASHTYPE(EXEC_KMALLOC),
CRASHTYPE(EXEC_VMALLOC),
CRASHTYPE(EXEC_RODATA),
CRASHTYPE(EXEC_USERSPACE),
CRASHTYPE(EXEC_NULL),
CRASHTYPE(ACCESS_USERSPACE),
CRASHTYPE(ACCESS_NULL),
CRASHTYPE(WRITE_RO),
CRASHTYPE(WRITE_RO_AFTER_INIT),
CRASHTYPE(WRITE_KERN),
CRASHTYPE(REFCOUNT_INC_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_OVERFLOW),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_DEC_ZERO),
CRASHTYPE(REFCOUNT_DEC_NEGATIVE),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_INC_ZERO),
CRASHTYPE(REFCOUNT_ADD_ZERO),
CRASHTYPE(REFCOUNT_INC_SATURATED),
CRASHTYPE(REFCOUNT_DEC_SATURATED),
CRASHTYPE(REFCOUNT_ADD_SATURATED),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_TIMING),
CRASHTYPE(ATOMIC_TIMING),
CRASHTYPE(USERCOPY_HEAP_SIZE_TO),
CRASHTYPE(USERCOPY_HEAP_SIZE_FROM),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_TO),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_FROM),
CRASHTYPE(USERCOPY_STACK_FRAME_TO),
CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
CRASHTYPE(USERCOPY_STACK_BEYOND),
CRASHTYPE(USERCOPY_KERNEL),
CRASHTYPE(USERCOPY_KERNEL_DS),
CRASHTYPE(STACKLEAK_ERASING),
CRASHTYPE(CFI_FORWARD_PROTO),
};
크래시 타입 별로 크래시를 유발하는 코드를 확인하는게 중요한데요.
CRASHTYPE(BUG)은 다음 코드에 매핑됩니다.
void lkdtm_BUG(void)
{
BUG();
}
CRASHTYPE(EXCEPTION)에 매핑되는 루틴은 다음과 같습니다.
void lkdtm_EXCEPTION(void)
{
*((volatile int *) 0) = 0;
}
제가 위에서 정의된 크래시 타입을 전부 실행했는데, 대부분 크래시가 유발되나 크래시가 유발되지
않는 경우도 확인했습니다.
LKDTM 실행하기
(LKDTM 실행하기 전에 먼저 ftrace 이벤트를 활성화하면 좋습니다. vmcore에서 ftrace를 함께 보면 더 많은 디버깅 정보를 확인할 수 있거든요. e.g: sched_switch, irq_handle_entry, irq_handle_exit)
LKDTM을 실행하는 방법은 간단합니다. 셸을 하나 열고 아래 포멧으로 명령어를 입력하면 됩니다.
$ echo '크래시 타입' > /sys/kernel/debug/provoke-crash/DIRECT
몇 가지 예를 들겠습니다. CRASHTYPE(BUG)을 유발하려면 다음과 같은 명령어를 입력하면 됩니다.
$ echo BUG > /sys/kernel/debug/provoke-crash/DIRECT
'/sys/kernel/debug/provoke-crash/DIRECT'에 BUG 문자열을 전달하면 아래 코드가 실행됩니다.
void lkdtm_BUG(void)
{
BUG();
}
만약 스택을 오염(Corrupt) 시켜서 크래시를 유발하고 싶으면 아래 명령어를 입력하면 되겠네요.
$ echo CORRUPT_STACK > /sys/kernel/debug/provoke-crash/DIRECT
위 명령어를 실행하면 lkdtm_CORRUPT_STACK() 함수가 실행되면서 스택을 깹니다.
static noinline void __lkdtm_CORRUPT_STACK(void *stack)
{
memset(stack, '\xff', 64);
}
noinline void lkdtm_CORRUPT_STACK(void)
{
/* Use default char array length that triggers stack protection. */
char data[8] __aligned(sizeof(void *));
__lkdtm_CORRUPT_STACK(&data);
pr_info("Corrupted stack containing char array ...\n");
}
data를 char 타입인 배열(8)로 잡았는데, __lkdtm_CORRUPT_STACK() 함수에서 64바이트 사이즈로 메모리 카피를 하니 스택에 '\xff'를 덮어 씁니다.
권장 크래시 타입
사실 LKDTM으로 지원되는 크래시 타입을 모두 테스트하면 좋은데요.
실전 프로젝트에서 잘 발생하는 순서로 테스트를 하면 좋을 것 같아요.
주석을 달았으니 참고하시면 좋겠네요.
static const struct crashtype crashtypes[] = {
CRASHTYPE(PANIC), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(BUG), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(WARNING), /* 테스트 요망 | 발생 가능 | 크래시 유발안됨 *'/
CRASHTYPE(WARNING_MESSAGE),
CRASHTYPE(EXCEPTION), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(LOOP),
CRASHTYPE(EXHAUST_STACK), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_STACK), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_STACK_STRONG),
CRASHTYPE(CORRUPT_LIST_ADD), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_LIST_DEL), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(CORRUPT_USER_DS),
CRASHTYPE(STACK_GUARD_PAGE_LEADING),
CRASHTYPE(STACK_GUARD_PAGE_TRAILING),
CRASHTYPE(UNSET_SMEP),
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
CRASHTYPE(OVERWRITE_ALLOCATION),
CRASHTYPE(WRITE_AFTER_FREE), /* 테스트 요망 | 종종 발생(메모리 Corruption) | 100% 유발 *'/
CRASHTYPE(READ_AFTER_FREE), /* 테스트 요망 | 종종 발생(메모리 Corruption) | 100% 유발 *'/
CRASHTYPE(WRITE_BUDDY_AFTER_FREE),
CRASHTYPE(READ_BUDDY_AFTER_FREE),
CRASHTYPE(SLAB_FREE_DOUBLE),
CRASHTYPE(SLAB_FREE_CROSS),
CRASHTYPE(SLAB_FREE_PAGE),
CRASHTYPE(SOFTLOCKUP), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(HARDLOCKUP), /* 테스트 요망 | 자주 발생 | 100% 유발 *'/
CRASHTYPE(SPINLOCKUP), /* 테스트 요망 | 종종 발생 | 100% 유발 *'/
CRASHTYPE(HUNG_TASK),
CRASHTYPE(EXEC_DATA),
CRASHTYPE(EXEC_STACK),
CRASHTYPE(EXEC_KMALLOC),
CRASHTYPE(EXEC_VMALLOC),
CRASHTYPE(EXEC_RODATA),
CRASHTYPE(EXEC_USERSPACE),
CRASHTYPE(EXEC_NULL),
CRASHTYPE(ACCESS_USERSPACE),
CRASHTYPE(ACCESS_NULL),
CRASHTYPE(WRITE_RO),
CRASHTYPE(WRITE_RO_AFTER_INIT),
CRASHTYPE(WRITE_KERN),
CRASHTYPE(REFCOUNT_INC_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_OVERFLOW),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_OVERFLOW),
CRASHTYPE(REFCOUNT_DEC_ZERO),
CRASHTYPE(REFCOUNT_DEC_NEGATIVE),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_NEGATIVE),
CRASHTYPE(REFCOUNT_INC_ZERO),
CRASHTYPE(REFCOUNT_ADD_ZERO),
CRASHTYPE(REFCOUNT_INC_SATURATED),
CRASHTYPE(REFCOUNT_DEC_SATURATED),
CRASHTYPE(REFCOUNT_ADD_SATURATED),
CRASHTYPE(REFCOUNT_INC_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_ADD_NOT_ZERO_SATURATED),
CRASHTYPE(REFCOUNT_DEC_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_SUB_AND_TEST_SATURATED),
CRASHTYPE(REFCOUNT_TIMING),
CRASHTYPE(ATOMIC_TIMING),
CRASHTYPE(USERCOPY_HEAP_SIZE_TO),
CRASHTYPE(USERCOPY_HEAP_SIZE_FROM),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_TO),
CRASHTYPE(USERCOPY_HEAP_WHITELIST_FROM),
CRASHTYPE(USERCOPY_STACK_FRAME_TO),
CRASHTYPE(USERCOPY_STACK_FRAME_FROM),
CRASHTYPE(USERCOPY_STACK_BEYOND),
CRASHTYPE(USERCOPY_KERNEL),
CRASHTYPE(USERCOPY_KERNEL_DS),
CRASHTYPE(STACKLEAK_ERASING),
CRASHTYPE(CFI_FORWARD_PROTO),
};
시그니처
LKDTM을 실행한 후 덤프(vmcore)에서 받은 커널 로그를 소개합니다.
아래는 'echo EXCEPTION > /sys/kernel/debug/provoke-crash/DIRECT' 명령어를 실행후 받은 덤프에서 확인한 커널 로그입니다.
[ 57.750675] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 57.757757] pgd = ffffffc045138000
[ 57.761957] [00000000] *pgd=0000000000000000
[ 57.765404] Internal error: Oops: 96000045 [#1] PREEMPT SMP
[ 57.770949] Modules linked in:
[ 57.773996] CPU: 7 PID: 1897 Comm: sh Tainted: G B W 3.18.49-g9381d81-00083-gb8bb6a4-dirty #2
[ 57.783215] task: ffffffc04e980000 ti: ffffffc0450ec000 task.ti: ffffffc0450ec000
[ 57.790665] PC is at lkdtm_do_action+0x44/0x130
[ 57.795191] LR is at direct_entry+0x100/0x128
[ 57.799511] pc : [<ffffffc0006019e8>] lr : [<ffffffc000601bd4>] pstate: 80000145
[ 57.806883] sp : ffffffc0450efdb0
[ 57.810213] x29: ffffffc0450efdb0 x28: ffffffc0450ec000
[ 57.815480] x27: ffffffc00163b000 x26: 0000000000000040
[ 57.820774] x25: 0000000000000116 x24: 0000000000000015
[ 57.826068] x23: 0000000000000000 x22: 0000000000000003
[ 57.831370] x21: ffffffc0450efec8 x20: ffffffc02f240000
[ 57.836661] x19: 000000000000000a x18: 00000000ffffffff
[ 57.841956] x17: 0000007fa1e89344 x16: ffffffc0002ffb30
[ 57.847252] x15: 0000007fa1d1c120 x14: 0fffffffffffffff
[ 57.852549] x13: 0000000000000038 x12: 0101010101010101
[ 57.857842] x11: 7f7f7f7f7f7f7f7f x10: ff4d4e48534f4442
[ 57.863146] x9 : 7f7fffffffffffff x8 : 5045435845207972
[ 57.868430] x7 : 746e652074636572 x6 : ffffffc00186ee1f
[ 57.873727] x5 : 000000000000000a x4 : 0000000000000004
[ 57.879023] x3 : 0000000000000000 x2 : ffffffc0450ec000
[ 57.884316] x1 : ffffffc0006019d4 x0 : 0000000000000000 [ 57.889316] mdss_mdp_video_wait4comp: vsync wait timeout 0, fallback to poll mode
이번에는 'echo WRITE_AFTER_FREE > /sys/kernel/debug/provoke-crash/DIRECT' 명령어를 실행한 다음에 받은 덤프에서 확인한 커널 로그입니다.
[ 84.609150] lkdtm: Performing direct entry WRITE_AFTER_FREE
[ 84.127146] BUG kmalloc-1024 (Tainted: G B W ): Poison overwritten
[ 84.133909] -----------------------------------------------------------------------------
[ 84.143551] INFO: 0xffffffc00dd31600-0xffffffc00dd319fe. First byte 0x78 instead of 0x6b
[ 84.151630] INFO: Allocated in lkdtm_do_action+0xd4/0x130 age=8 cpu=4 pid=4509
[ 84.158827] +alloc_debug_processing+0x114/0x16c
[ 84.163340] +__slab_alloc.isra.52.constprop.58+0x3f4/0x504
[ 84.168805] +kmem_cache_alloc_trace+0x9c/0x1e8
[ 84.173234] +lkdtm_do_action+0xd0/0x130
[ 84.177053] +direct_entry+0xfc/0x128
[ 84.180617] +vfs_write+0xcc/0x178
[ 84.183912] +SyS_write+0x44/0x74
[ 84.187132] +cpu_switch_to+0x48/0x4c
[ 84.190689] INFO: Freed in lkdtm_do_action+0xdc/0x130 age=13 cpu=4 pid=4509
[ 84.197629] +free_debug_processing+0x22c/0x2cc
[ 84.202054] +__slab_free+0x44/0x2b0
[ 84.205525] +kfree+0x1f8/0x218
[ 84.208566] +lkdtm_do_action+0xd8/0x130
[ 84.212387] +direct_entry+0xfc/0x128
[ 84.215943] +vfs_write+0xcc/0x178
[ 84.219240] +SyS_write+0x44/0x74
[ 84.222454] +cpu_switch_to+0x48/0x4c
[ 84.226016] INFO: Slab 0xffffffbc05d12780 objects=23 used=23 fp=0x (null) flags=0x4080
[ 84.243379] Bytes b4 ffffffc00dd315f0: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZZZZZZZZZ
[ 84.837991] Object ffffffc00dd319f0: 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 xxxxxxxxxxxxxxxx
[ 84.847280] Redzone ffffffc00dd31a00: bb bb bb bb bb bb bb bb ........
[ 84.855961] Padding ffffffc00dd31b40: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZZZZZZZZZ
[ 84.884090] Padding ffffffc00dd31b70: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZZZZZZZZZ
[ 84.893467] CPU: 7 PID: 2510 Comm: Binder_10 Tainted: G B W 3.10.49-g9381d81-00083-gb8bb6a4-dirty #
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널] 특정 슬럽 오브젝트 트레이싱 (0) | 2023.05.07 |
---|---|
[리눅스커널] ftrace: boottime-trace(부트타임 ftrace) (0) | 2023.05.07 |
[리눅스커널] IPI Call 요청: SGI(Software Generated Interrupt) 요청 (로우 레벨 뷰) (0) | 2023.05.07 |
[리눅스커널] IPI(Inter Processor Interrupt) Call 요청 루틴(하이 레벨 뷰) (0) | 2023.05.07 |
[LinuxKernel] Tip: How to disable CONFIG_SCHED_INFO (0) | 2023.05.07 |