Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8179
1390
307630


[리눅스커널][디버깅] 크래시 유틸리티로 vmcore를 T32(Trace32)에 로딩하기 3. 커널 디버깅과 코드 학습

소개 

크래시 유틸리티는 리눅스 커널 개발에서 인기 있는 디버깅 툴입니다. 특히 search 명령어로 메모리 서치를 할 수 있는 막강한 기능을 제공합니다. 하지만 프로세스별 콜스택을 이동하면서 지역변수를 볼 수 있는 기능이 없어 조금 불편합니다.

이번 포스팅에서 vmcore에서 스택 덤프를 추출해 T32 시뮬레이터로 콜스택을 올려 보는 방법을 소개합니다.

크래시 유틸리티로 프로세스 스택 메모리 덤프하기

"./crash64 vmcore vmlinux" 명령어로 크래시 유틸리티를 실행합니다.
austindh.kim~/backup/Vmcore_dump$ ./crash64 vmcore vmlinux

crash64 7.1.9++
Copyright (C) 2002-2017  Red Hat, Inc.
Copyright (C) 2004, 2005, 2006, 2010  IBM Corporation
Copyright (C) 1999-2006  Hewlett-Packard Co
Copyright (C) 2005, 2006, 2011, 2012  Fujitsu Limited
Copyright (C) 2006, 2007  VA Linux Systems Japan K.K.
Copyright (C) 2005, 2011  NEC Corporation
Copyright (C) 1999, 2002, 2007  Silicon Graphics, Inc.
Copyright (C) 1999, 2000, 2001, 2002  Mission Critical Linux, Inc.
This program is free software, covered by the GNU General Public License,
and you are welcome to change it and/or distribute copies of it under
certain conditions.  Enter "help copying" to see the conditions.
This program has absolutely no warranty.  Enter "help warranty" for details.

GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=aarch64-elf-linux"...

WARNING: kernel version inconsistency between vmlinux and dumpfile

please wait... (gathering kmem slab cache data)
crash64: invalid kernel virtual address: 48773a9004  type: "array cache limit"
....
WARNING: cannot determine starting stack frame for task ffffffc87bb4cc00
      KERNEL: vmlinux
    DUMPFILE: vmcore
        CPUS: 4
        DATE: Thu Jan 15 05:02:48 1987
      UPTIME: 00:02:01
LOAD AVERAGE: 0.01, 0.01, 0.01
       TASKS: 107
    NODENAME: Xilinx-ZCU102-2016_3
     RELEASE: 4.6.0
     VERSION: #1 SMP Tue Apr 4 17:38:35 KST 2017
     MACHINE: aarch64  (unknown Mhz)
      MEMORY: 4 GB
       PANIC: "sysrq: SysRq : Trigger a crash"
         PID: 0
     COMMAND: "swapper/0"
        TASK: ffffff8008bd3400  (1 of 4)  [THREAD_INFO: ffffff8008bc4000]
         CPU: 0
       STATE: TASK_RUNNING (ACTIVE)

첫 메시지를 보니 "echo /proc/sysrq-trigger" 명령어로 강제 커널 패닉을 유발했습니다.

pid가 1549인 sh 프로세스의 콜스택을 보겠습니다.
crash64> bt 1549
PID: 1549   TASK: ffffffc87bae6f00  CPU: 1   COMMAND: "sh"
 #0 [ffffffc87bbdbb40] __switch_to at ffffff8008086b34
 #1 [ffffffc87bbdbb60] __schedule at ffffff80088778a0
 #2 [ffffffc87bbdbbb0] schedule at ffffff8008877cbc
 #3 [ffffffc87bbdbbd0] schedule_timeout at ffffff800887a5a8
 #4 [ffffffc87bbdbc50] wait_woken at ffffff80080d273c
 #5 [ffffffc87bbdbc80] n_tty_read at ffffff80084892f8
 #6 [ffffffc87bbdbd90] tty_read at ffffff8008482d10
 #7 [ffffffc87bbdbdd0] __vfs_read at ffffff800818add0
 #8 [ffffffc87bbdbe50] vfs_read at ffffff800818bc38
 #9 [ffffffc87bbdbe90] sys_read at ffffff800818d0f8
#10 [ffffffc87bbdbed0] el0_svc_naked at ffffff8008085e2c
     PC: 0000007f87126bb8   LR: 00000000004a6edc   SP: 0000007fc2acbf10
    X29: 0000007fc2acbf10  X28: 0000007fc2acc1e0  X27: 00000000004e5000
    X26: ffffffffffffffff  X25: 0000000000000001  X24: 00000000004e8a84
    X23: 0000000000000001  X22: 00000000004de5f4  X21: 00000000004ea000
    X20: 0000007f871ae7d0  X19: 00000000004e9a38  X18: 0000000000000001
    X17: 0000007f87126bd0  X16: 0000000000000000  X15: 0000000000000070
    X14: 0000000000000000  X13: 0000000000000000  X12: 0000000000000000
    X11: 0000000000000000  X10: 0000000000000000   X9: 0000000000000004
     X8: 000000000000003f   X7: 30322d3230315543   X6: 0000007f8701c01d
     X5: 248290f6fb60f00d   X4: 00000000004e0000   X3: 00000000004e0000
     X2: 0000000000000001   X1: 0000007fc2acbf4f   X0: 0000000000000000
    ORIG_X0: 0000000000000000  SYSCALLNO: 3f  PSTATE: 60000000
    
pid가 1549인 "sh" 프로세스의 태스크 디스크립터 정보를 보겠습니다.
crash64> task 1549
PID: 1549   TASK: ffffffc87bae6f00  CPU: 1   COMMAND: "sh"
struct task_struct {
  state = 0x1,
  stack = 0xffffffc87bbd8000,  //<<--
  usage = {
...
  thread = {
    cpu_context = {
      x19 = 0xffffffc87bac4680,
      x20 = 0xffffffc87bae6f00,
      x21 = 0xffffffc87bae6f00,
      x22 = 0xffffffc879160100,
      x23 = 0xffffffc87bae7398,
      x24 = 0xffffff8008bca000,
      x25 = 0x0,
      x26 = 0xffffff80092a6000,
      x27 = 0x0,
      x28 = 0xffffffc87b16d400,
      fp = 0xffffffc87bbdbb40,
      sp = 0xffffffc87bbdbb40,
      pc = 0xffffff8008086b38
    },    

프로세스 스택 최상단 주소가 0xffffffc87bbd8000이고 마지막 실행 레지스터 세트 정보는 x19~pc 필드에서 볼 수 있습니다.

프로세스 스택을 덤프해보겠습니다. Aarch64 비트 아키텍처이니 스택 사이즈는 0x4000입니다.
따라서 다음 주소 범위 스택을 덤프하면 됩니다.
0xffffffc87bbd8000--0xffffffc87bbd8000+0x4000

크래시 유틸리티에서 지원하는 rd 명령어를 활용해 메모리 덤프를 뽑아냅니다.
crash64> rd 0xffffffc87bbd8000 -e 0xffffffc87bbdd000 -r stack_dump.bin
20480 bytes copied from 0xffffffc87bbd8000 to stack_dump.bin

0xffffffc87bbd8000 주소는 메모리 시작 주소, 0xffffffc87bbdd000는 메모리 끝 주소입니다.
이 범위 메모리 덤프를 stack_dump.bin 파일로 저장합니다.

Trace32(T32)로 스택 덤프 로딩해 콜스택 복원하기

이제 T32 시뮬레이터가 등장할 차례입니다.

먼저 다음 명령어로 시스템 초기화를 합시다.
sys.cpu cortexa53
sys.u

다음 stack_dump.bin 덤프를 T32로 로딩합니다.
d.load.bin stack_dump.bin /noclear 0xffffffc87bbd8000

/noclear 옵션은 이전 메모리 덤프를 무시하고 덮어쓰지 않는 옵션이고 0xffffffc87bbd8000는 메모리 오프셋 주소입니다.
 
크래시 유틸리티에서 0xffffffc87bbd8000 메모리 주소 덤프를 추출했으니 T32에서 0xffffffc87bbd8000는 주소 오프셋을 줍니다. 
crash64> rd 0xffffffc87bbd8000 -e 0xffffffc87bbdd000 -r stack_dump.bin
20480 bytes copied from 0xffffffc87bbd8000 to stack_dump.bin 

다음 vmlinux를 로딩합시다.
Data.LOAD.elf vmlinux /nocode /noclear

프로세스 스택 덤프를 로딩했으니 이제는 T32로 콜스택을 볼 차례입니다.
먼저 크래시 유틸리티에서 본 프로세스 레지스터 정보를 떠올립시다.
  thread = {
    cpu_context = {
      x19 = 0xffffffc87bac4680,
      x20 = 0xffffffc87bae6f00,
      x21 = 0xffffffc87bae6f00,
      x22 = 0xffffffc879160100,
      x23 = 0xffffffc87bae7398,
      x24 = 0xffffff8008bca000,
      x25 = 0x0,
      x26 = 0xffffff80092a6000,
      x27 = 0x0,
      x28 = 0xffffffc87b16d400,
      fp = 0xffffffc87bbdbb40,
      sp = 0xffffffc87bbdbb40,
      pc = 0xffffff8008086b38
    },    

위 레지스터 세트 정보를 참고해서 pc부터 x28까지 레지스터를 T32에서 로딩합시다.
다음 명령어를 T32 시뮬레이터에서 입력합시다.
r.s  pc 0xffffff8008086b38   
r.s sp   0xffffffc87bbdbb40  
r.s x29 0xffffffc87bbdbb40
r.s x28 0xffffffc87b16d400
...

T32에서 "v.f" 명령어를 입력해 콜스택을 봅시다. 
-000|__switch_to(prev = 0x0, next = 0x0)
-001|__schedule(preempt = FALSE)
-002|test_bit(inline)
-002|test_ti_thread_flag(inline)
-002|need_resched(inline)
-002|schedule()
-003|schedule_timeout(timeout = 9223372036854775807)
-004|wait_woken(wait = 0xFFFFFFC87BBDBD68, ?, ?)
-005|test_ti_thread_flag(inline)
-005|test_tsk_thread_flag(inline)
-005|signal_pending(inline)
-005|n_tty_read(tty = 0xFFFFFFC87B16D400, file = 0xFFFFFFC87BA05000, buf = 0x0000007FC2ACBF4F, nr = 18446743524089577028)
-006|tty_read(file = 0xFFFFFFC87BA05000, buf = 0x0000007FC2ACBF4F, count = 1, ?)
-007|__vfs_read(file = 0x0000007FC2ACBF4F, buf = ???, count = ???, pos = ???)
-008|vfs_read(file = 0xFFFFFFC87BA05000, buf = 0x0000007FC2ACBF4F, ?, pos = 0xFFFFFFC87BBDBEC8)
-009|SYSC_read(inline)
-009|sys_read(?, buf = 548726947663, count = 1)
-010|el0_svc_naked(asm)
 ---|end of frame

v.f 창에서 보이는 Up 아이콘을 눌러서 콜스택을 이동합시다.
-006|tty_read(file = 0xFFFFFFC87BA05000, buf = 0x0000007FC2ACBF4F, count = 1, ?)
-007|__vfs_read(file = 0x0000007FC2ACBF4F, buf = ???, count = ???, pos = ???)
-008|vfs_read(file = 0xFFFFFFC87BA05000, buf = 0x0000007FC2ACBF4F, ?, pos = 0xFFFFFFC87BBDBEC8)
-009|SYSC_read(inline)
-009|sys_read(?, buf = 548726947663, count = 1)
-010|el0_svc_naked(asm)
 ---|end of frame

vmcore에서 프로세스 스택만 덤프해 T32에서 콜스택을 보는 방법을 소개했습니다.
이 방식을 쓰면 vmcore에 있는 메모리 덤프를 T32에서 볼 수 있습니다.


#커널 크래시 디버깅 및 TroubleShooting

덧글

댓글 입력 영역