이 장에서는 크래시 덤프로부터 커널 디버깅 작업을 하기 위한
명령들을 설명합니다. 이 명령들을 사용하기 위해서는 크래시 덤프를
위한 충분한 디스크 용량이 필요합니다. 만약 여러 개의
스왑 파티션이 있는 시스템에서 첫번째 스왑 파티션이
덤프를 저장하기에 용량이 작을 때에는 다른 파티션에
덤프를 받도록 커널 설정을 변경하거나(config kernel 라인),
dumpon(8) 명령을 사용하여 다른 파티션을 지정할 수 있습니다.
dumpon(8) 명령을 사용할 경우, /etc/rc.conf의
dumpdev 변수에 설정해주는 것이 제일 좋은 방법입니다.
일반적으로 /etc/fstab 파일에 지정되어 있는 스왑 장치들 중
하나를 지정해주면 됩니다.
테이프와 같이 스왑 장치가 아닌 다른 장치에 덤프하는 것은 현재
지원되지 않습니다. config -g를 실행하여 커널을
설정하시기 바랍니다.
FreeBSD 커널 설정에 관한 자세한 내용은
FreeBSD 커널 설정을 참고하시기
바랍니다.
커널에 어느 장치로 덤프할 것인지 지정해주기 위해서 dumpon(8)
명령을 사용합니다 (주: swapon(8) 명령을 통해 어떤
파티션을 스왑 영역으로 사용할 것인지 설정한 다음에 사용해야
합니다). 일반적으로 /etc/rc.conf와 /etc/rc
파일에 나란히 설정됩니다.
다른 방법으로, 커널 설정 파일 내의 'config' 라인의 'dump' 절에
덤프 장치를 하드 코딩할 수 있습니다.
이는 권장되지 않는 방법이며, 부팅 도중에 손상된
커널으로부터 크래시 덤프를 얻고자 할 때만 사용하는 편이 바람직합니다.
주: 이제부터 `kgdb' 라는 용어는
'커널 디버깅 모드'에서 실행되는 gdb를 의미합니다.
gdb를 -k 옵션과 같이 실행
시키거나, kgdb라는 이름으로 링크시켜 실행시킬 수 있습니다.
그러나 kgdb라는 이름은 기본으로 제공된 것이 아니며,
GNU 구성원들은 자신의 프로그램들이 서로 다른 이름으로 불리우면서
다른 기능을 제공하는 것을 원치 않습니다.
이런 특징은 향후 릴리즈들에서는 사라질 것입니다.
커널 빌드 시 kernel.debug 등의 이름으로 복사본을
하나 만든 다음, 커널 원본에 대하여 strip -d 명령을 수행하고
정상적으로 인스톨합니다. 심볼 테이블을 삭제하지
않은 커널로 인스톨이 가능하지만, 몇몇 프로그램에 대한
심볼 테이블 검색 시간이 심각하게 증가하게 되고, 부팅 시
심볼 테이블을 포함한 전체 커널이 로딩되어 나중에 스왑할 수 없게 되며,
수메가 바이트의 물리적 메모리가 낭비됩니다.
부트 프롬프트에서 새로운 커널 이름을 타이핑하여 부팅하는 식으로
새로운 커널을 테스트할 수 있습니다. 그러나 다시 시스템을
정상적으로 동작시키길 원할 때에는, 부트 프롬프트에서
-s 플랙을 이용하여 단일 유저 상태로 부팅합니다.
그 이후부터 다음의 절차를 수행합니다.
fsck -p
mount -a -t ufs # so your file system for /var/crash is writable
savecore -N /kernel.panicked /var/crash
exit # ...to multi-user
위의 명령들은 savecore(8)를 통해 다른 커널로부터 심볼
이름을 추출합니다. 그렇지 않을 경우 심볼을 얻어내는 기본 대상으로
현재 동작하고 있는 커널이 사용되며, 크래시 덤프에서의 커널 심볼과
현재 동작 중인 커널 심볼은 다르기 때문에 전혀 쓸모가 없어지게 됩니다.
크래시 덤프가 발생하면, /sys/compile/WHATEVER 디렉토리로 가서
kgdb를 실행시킵니다. kgdb에서 다음을 수행합니다.
symbol-file kernel.debug
exec-file /var/crash/kernel.0
core-file /var/crash/vmcore.0
이로써, 다른 일반 프로그램을 디버깅할 때와 같이 커널 소스를 이용하여
크래시 덤프를 디버깅할 수 있게 됩니다.
아래에 그 과정을 설명해주는 kgdb 세션의 스크립트 로그가
있습니다. 길이가 긴 라인은 가독성을 높이기 위해 다음 라인으로
내렸습니다. 그리고 각 줄마다 줄번호를 달았습니다. 아래의 로그는
실제 pcvt 콘솔 드라이버 개발 과정의 오류 추적 작업입니다.
1:Script started on Fri Dec 30 23:15:22 1994
2:uriah # cd /sys/compile/URIAH
3:uriah # kgdb kernel /var/crash/vmcore.1
4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel...done.
5:IdlePTD 1f3000
6:panic: because you said to!
7:current pcb at 1e3f70
8:Reading in symbols for ../../i386/i386/machdep.c...done.
9:(kgdb) where
10:#0 boot (arghowto=256) (../../i386/i386/machdep.c line 767)
11:#1 0xf0115159 in panic ()
12:#2 0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698)
13:#3 0xf010185e in db_fncall ()
14:#4 0xf0101586 in db_command (-266509132, -266509516, -267381073)
15:#5 0xf0101711 in db_command_loop ()
16:#6 0xf01040a0 in db_trap ()
17:#7 0xf0192976 in kdb_trap (12, 0, -272630436, -266743723)
18:#8 0xf019d2eb in trap_fatal (...)
19:#9 0xf019ce60 in trap_pfault (...)
20:#10 0xf019cb2f in trap (...)
21:#11 0xf01932a1 in exception:calltrap ()
22:#12 0xf0191503 in cnopen (...)
23:#13 0xf0132c34 in spec_open ()
24:#14 0xf012d014 in vn_open ()
25:#15 0xf012a183 in open ()
26:#16 0xf019d4eb in syscall (...)
27:(kgdb) up 10
28:Reading in symbols for ../../i386/i386/trap.c...done.
29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\
30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\
31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\
32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\
33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\
34:ss = -266427884}) (../../i386/i386/trap.c line 283)
35:283 (void) trap_pfault(&frame, FALSE);
36:(kgdb) frame frame->tf_ebp frame->tf_eip
37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done.
38:#0 0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\
39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403)
40:403 return ((*linesw[tp->t_line].l_open)(dev, tp));
41:(kgdb) list
42:398
43:399 tp->t_state |= TS_CARR_ON;
44:400 tp->t_cflag |= CLOCAL; /* cannot be a modem (:-) */
45:401
46:402 #if PCVT_NETBSD || (PCVT_FREEBSD >= 200)
47:403 return ((*linesw[tp->t_line].l_open)(dev, tp));
48:404 #else
49:405 return ((*linesw[tp->t_line].l_open)(dev, tp, flag));
50:406 #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */
51:407 }
52:(kgdb) print tp
53:Reading in symbols for ../../i386/i386/cons.c...done.
54:$1 = (struct tty *) 0x1bae
55:(kgdb) print tp->t_line
56:$2 = 1767990816
57:(kgdb) up
58:#1 0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\
59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126)
60: return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p));
61:(kgdb) up
62:#2 0xf0132c34 in spec_open ()
63:(kgdb) up
64:#3 0xf012d014 in vn_open ()
65:(kgdb) up
66:#4 0xf012a183 in open ()
67:(kgdb) up
68:#5 0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\
69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\
70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \
71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \
72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673)
73:673 error = (*callp->sy_call)(p, args, rval);
74:(kgdb) up
75:Initial frame selected; you cannot go up.
76:(kgdb) quit
77:uriah # exit
78:exit
79:
80:Script done on Fri Dec 30 23:18:04 1994
다음은 위 스크립트에 대한 설명입니다.
위 스크립트는 DDB(아래 참조) 내에서 얻은 덤프이며, ``because you said to!''는 패닉(panic) 메시지이며 다소 긴 스택 추적(trace)을 보여줍니다. DDB로 옮겨간 이유는 커널이 페이지 폴트 트랩에 빠졌기 때문입니다.
스택 추적에서 trap() 함수의 위치입니다.
새로운 스택 프레임을 강제로 사용하는 방법입니다. 현재는 더 이상 필요로 하지 않습니다. 트랩에 잡힌 경우라 하더라도 스택 프레임이 현재 올바른 위치를 가리키고 있다고 가정합니다. (저는 <g> 에 유용한 코어 덤프를 가지고 있지 않습니다. 제 커널은 꽤 오랜 시간 동안 패닉에 빠진 적이 없었습니다.) 소스 코드의 403줄에서 볼 수 있는 바와 같이, ``tp''를 가리키는 포인터가 엉켰거나, 배열 억세스에서 경계를 넘어섰을 가능성이 높습니다.
포인터가 의심스럽지만, 올바른 주소를 가리키고 있습니다.
그러나, 분명히 포인터는 쓸모 없는 영역을 가리키고 있고,
따라서 이부분에서 에러를 찾아낼 수 있습니다.
(이런 특수한 코드에 대해 익숙하지 못한 분들을 위해, 여기서
tp->t_line은 콘솔 장치의 라인 규칙을 나타내는
것이며, 작은 정수 값을 가져야 하는 점을 알려 드립니다.)