커널 디버거인 KGDB 를 이용해서 원격으로 커널 레벨 디버깅을 하는 방법에 대해 다룬다.

커널 디버거란

흔히 커널의 정의를 얘기할 때, 하드웨어와 소프트웨어의 중간에 있는 것이라고 한다. 이것은 정확한 정의가 아니다. 커널을 소프트웨어라고도 할 수 없고, 그렇다고 하드웨어라고도 할 수 없다. 그렇다면, 이것을 어떻게 디버깅할 것인가? 에대한 궁금증이 생긴다.
문제의 원인이 소프트웨어라고 한다면, GDB 를 사용해서 디버깅을 하면 되고, 하드웨어라고 한다면, 테스터기나, 오실로스코프 같은 장비를 이용해서 디버깅할 수 있을 것이다.
커널 디버거를 GDB 와 같은 일반 디버거들과 따로 분리하는 이유는 기존의 디버거들과 다른 방법을 사용하기 때문이다.

여기서는 먼저 KGDB 라는 커널 디버거를 사용해서 어떻게 원격으로 커널 디버깅을 하는지에 대해서 설명하도록 하겠다.
KGDB 는 뒤에서 다룰 KDB 보다, 소스레벨 디버깅이 가능하다는 장점을 가지고 있다. 그렇지만 동시에 세부 설정이 복잡하다는 점과 최소 2대의 타겟과 호스트가 필요하다는 단점도 가지고 있다.

준비운동 하기

이 문서에서는 타겟과 호스트가 모두 X86 인 경우에 KGDB 를 사용하여 커널 디버깅을 해볼 것이다.
테스트 환경은 다음과 같다.

호스트 타겟
아키텍처 X86 X86
OS Debian(stable) Debian(stable)
커널 2.6.7 2.6.7
ETC 크로스 케이블(serial)

KGDB 는 http://kgdb.linsyssoft.com/http://kgdb.linsyssoft.com/downloads/kgdb-2/ 에서 다운받을 수 있다.
모든 커널에서 KGDB 를 공식으로 지원하는 것이 아니므로, 현재(2006년 11월 9일)에 나온 것들 중에, linux-2.6.7-kgdb-2.2.tar.bz2 을 다운로드 받았다.

전체적인 작업의 순서를 보자면, 다음과 같다.

  1. 호스트에서 2.6.7 커널에 kgdb 패치를 한다.
  2. kgdb 관련 커널 옵션을 선택하고, 커널 컴파일을 한다. 여기서는 OS 가 Debian 이기 때문에 'make-kpkg' 를 이용한다.
  3. 앞에서 생성한 커널이미지와 커널 모듈을 타겟으로 전송한다.
  4. 타겟에서 전송한 커널이미지와 커널 모듈을 설치한다. (ex : dpkg -i kernel-head…)
  5. 호스트와 타겟을 시리얼 크로스 케이블로 연결하고, 연결 테스트를 한다.
  6. 타겟의 menu.lst 파일을 수정한다.
  7. 호스트에서 gdb 설정파일을 만든다. (ex : .gdbinit)

이 문서 역시, 위의 작업 순서에 따라 설명한다.

KGDB 패치 및 커널 컴파일

앞에서 받은 linux-2.6.7-kgdb-2.2.tar.bz2 의 압축을 풀고 커널에 패치한다. 작업 디렉토리는 /home/fat81 이다.

#cd /home/fat81
#tar xzf linux-2.6.7.tar.gz
#bzip -d linux-2.6.7-kgdb-2.2.tar.bz2 
#tar xf linux-2.6.7-kgdb-2.2.tar
#mv linux /usr/local/linux-2.6.7
#cd /usr/src
#ln -s linux-2.6.7 linux
#cd linux
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/core-lite.patch
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/i386-lite.patch
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/8250-lite.patch
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/eth.patch
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/i386.patch
#patch -p1 < /home/fat81/linux-2.6.7-kgdb-2.2/core.patch
#make menuconfig

앞에서도 잠깐 언급했지만, 호스트에 Debian 이 설치되어 있기 때문에 커널 컴파일을 하기 위한 'make-kpkg' 와 'libncurses' 같은 패키지들은 미리 설치되어 있어야 한다.
앞에서 KGDB 패치를 가했기 때문에, 생소한(?) 커널 옵션들이 보일 것이다.
'Kernel hacking → KGDB : Remote (serial) kernel debugging with gdb' 가 바로 그것이다. (*) 선택을 하면, 하위 옵션들이 생겨난다. 그 중에서 몇가지 설정을 해주어야 한다. 다음은 KGDB 를 위해 설정해야하는 커널 옵션들이다.

[*]KGDB : Remote (serial) kernel debugging with gdb
 [*]KGDB : On generic serial port (8250)
 [*]Simple selection of KGDB serial port
 [*]Debug serial port BAUD (115200)
 [*]Serial port for KGDB (ttyS0)
 [*]KGDB : Thread analysis

나머지 옵션들은 모두 기본 설정으로 한다. 저장하고 빠져 나온 후에, Makefile 을 수정해야 한다.

...
HOSTCC          = gcc
HOSTCFLAGS      = -Wall -Wstrict-prototypes -O2 -g -ggdb    /* 수정 */
...

'-fomit-frame-pointer' 을 삭제해야 스택 추적이 가능하다.
이제 커널 컴파일 할 차례다.

#make-kpkg binary-arch

컴파일이 완료되면, header 와 image 파일이 .deb 형태로 생성된다. 이 파일들을 타겟으로 전송한다.

시리얼 포트 확인

모든 통신이 시리얼 포트를 통해서 이루어지기 때문에, 반드시 확인해봐야 한다.
먼저 크로스 시리얼 케이블을 호스트와 타겟에 연결한다. 그리고 호스트 쪽에서 test 라는 파일을 만들고, 시리얼 포트 설정을 한다.

#cat test
test1
test2
#stty ispeed 115200 ospeed 115200 -F /dev/ttyS0

이번에는 타겟쪽에서도 역시 시리얼 포트 설정을 한다.

#stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
#cat /dev/ttyS0

위와 같은 명령을 실행했을 때, 마지막 줄에 커서가 깜빡깜빡 거리면서 계속 멈춰 있어야 한다.
이 상태에서 호스트에서

#cat test > /dev/ttyS0

위와 같이 실행한다. 이때 타겟쪽 콘솔에서

test1
 
test2

위와 같이 출력되었다면, 성공이다.

여기서는 호스트와 타겟이 모두 X86 기반의 데스크탑 컴퓨터였다. 하지만, 처음에 필자는 노트북을 타겟으로 사용하려고 시도했었다. 하지만, 시리얼 연결 테스트에서 번번히 실패하고 말았다. 나중에 알게된 사실은 커널에서 kgdb 옵션을 지정할 때, 8250 으로 설정했는 데, 대부분의 메인보드에 들어가는 UART 칩셋은 8250 이다. 하지만, 내가 사용한 노트북의 UART 칩셋은 8250 이 아니었나보다.

커널 설치 및 GRUB 수정

앞에서 컴파일한 커널을 타겟에서 설치한다.

#dpkg -i kernel-headers-2.6.7_10.00.Custom_i386.deb
#dpkg -i kernel-image-2.6.7_10.00.Custom_i386.deb

그리고 부팅시에 옵션을 주기위해 부트로더(grub)의 설정(menu.lst)을 추가해야 한다.

title           Debian GNU/Linux, kernel 2.6.7
root            (hd0,0)
kernel          /vmlinuz-2.6.7 root=/dev/hda3 ro kgdbwait kgdb8250=0,115200     /* 추가 */
initrd          /initrd.img-2.6.7
savedefault
boot

GDB 설정파일 생성

호스트에서 타겟과 통신하기 위해서 설정파일을 만들어야 한다.
여기서는 2.6.7 커널을 디버깅할 것이기 때문에, /usr/src/linux-2.6.7 아래에 .gdbinit 파일을 만든다.

set remotebaud 115200
symbol-file vmlinux           /* 커널 심볼 파일 */
target remote /dev/ttyS0
set output-radix 16

이제 모든 준비가 끝났다. 실제로 디버깅 하는 일만 남았다.
먼저 타겟을 재부팅해서 앞에서 설정한 커널로 부팅하도록 한다. 그리고 호스트에서는 /usr/src/linux-2.6.7 에서 'gdb' 를 입력한다.

#gdb
...
...
...
breakpoint() at kernel/kgdb.c:1212
1212
(gdb)

위와 비슷하게 출력된다면, 성공이다. 이제 's' 같은 gdb 명령어를 사용해서 직접 소스레벨 디버깅을 할 수 있다.

  • computer/rtcclab/커널_디버거_사용하기_-_1.kgdb.txt
  • Last modified: 3 years ago
  • by likewind