Qemu booted kernel debugging with GDB


책을 읽고, 그 내용의 소스를 분석하다 보면 어떤 값에 의해 진행이되고 예제를 만들어 보는 과정에서 도저히 답이 안나오는 상황들이 생긴다. 그 때 대충 시나리오를 만들고 만든 시나리오에 값을 넣어 code를 분석하는데 값이 명확하게 떨어지면 굉장히 분석이 잘된다.(지난 buddy 관련 code를 분석할 때 그러했다.http://woodz.tistory.com/57)


하지만 그렇게 분석이 순조롭게 흘러가지 못하고 방황을 하다보면 집중도 안되고 하다 말아버리는 경우가 다반사다. 그래서 얼마전에 찾다보니 gdb 를 연결하여 kernel code를 쫓아가는 방법을 알게 되었다. 


eclipse + CDT 에 gdb를 연결하여 추적하는 방법은 UI 사용성이 좋고 보기 편하지만 너무 무겁다는 것이 단점이다. 조금 빨리 보고 싶은데 답답해서 eclipse 를 빼고 최대한 편한 방법을 찾았다.

(eclipse 를 이용해 kernel debugging 및 trace 하고 싶으신 분은, http://www.sw-at.com/blog/2011/02/11/linux-kernel-development-and-debugging-using-eclipse-cdt/ 여기를 참조하여 하면 좋다. 그림으로 잘나와있기도 하고)


이 포스팅은 kernel target 을 x86 으로 build 하고 qemu 로 순조롭게(?) 부팅하여 gdb 연결하는 것까지 준비했다.


1. kernel 준비

  - kernel source 를 받자.

$ mkdir ~/work/Kernel

$ cd ~/work/Kernel

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git


  요샌 30분 이내로 다운로드 가능하더라.


  - kernel build

$ make ARCH=i386 menuconfig

   다른 것들은 default 로 내비두고, gdb 를 사용해야 하므로 두가지 option 이 check 되어 있는지만 확인하자.

  a. Kernel hacking ==> Compile-time checks and compiler options ==> [*] Compile the kernel with debug info

  b. Kernel hacking ==> Compile-time checks and compiler options ==> -*- Compile the kernel with frame pointers


b는 default 로 되어 있었다. 안되어 있는 분은 check 해주자.

% menuconfig 하면 ncurses-devel 이 설치 안되어 있다고  error를 낼때는,

  ==> $ sudo apt-get install ncurses-dev


$ make ARCH=i386 -j4


2. qemu 준비
 - Ubuntu 의 경우

sudo apt-get install qemu qemu-system


3. kernel 부팅.(x86_64 system)

$ qemu-system-x86_64 -no-kvm -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0" -serial stdio

% 32bit machine 의 경우 qemu-i386 command 로 하면 된다.


위에처럼 하면 kernel 이 부팅된다. 물론 root filesystem 이 없기 때문에 mount 를 못하고 panic 을 발생시킨다. 

(control + C 하면 qemu 가 종료한다.)


root file system 없이도 kernel 을 gdb 에 연결할 수 있다.

우선 rootfs 없이 진행을 해보자.


4. gdb 연결

$ qemu-system-x86_64 -s -S -no-kvm -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0" -serial stdio


위의 command 에서 -s 는 gdb 를 default 로 쓰되 tcp port 1234 으로 연결하겠다는 의미의 옵션이다.(-gdb tcp::1234)

-S 옵션은 cpu 를 start 해줄때까지 멈춰있겠다는 의미이다.


실행하면 검은 화면만 떨렁 있는 화면을 볼껏이다. 그럼 이제 gdb 를 연결해보자.

$ cd ~/work/Kernel/linux/

$ gdb ./vmlinux

GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04

Copyright (C) 2012 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 "x86_64-linux-gnu".

For bug reporting instructions, please see:

<http://bugs.launchpad.net/gdb-linaro/>...

Reading symbols from /home/daeseok/work/Kernel/linux/vmlinux...done.

(gdb) target remote localhost:1234

Remote debugging using localhost:1234

0x0000fff0 in ?? ()


gdb command 이후에 "target remote localhost:1234" 를 치면, qemu 와 연결이 완료된다.

아직도 qemu 는 검은색화면이다. 

그다음에는 break point 하나를 잡자. 

(gdb) b start_kernel

Breakpoint 1 at 0xc18cb6f2: file init/main.c, line 484.

(gdb) c

Continuing.


Breakpoint 1, start_kernel () at init/main.c:484

484 {

(gdb)


break point 잡고 c(continue) 를 입력하면 실제 진행하다 start_kernel() 에서 멈춰있는 것을 확인 할 수 있다.

gdb command 로 요리하면서 부팅 sequence 를 살펴봐도 되고.. 뭐 암튼 그러하다.


(gdb) list

479 pgtable_init();

480 vmalloc_init();

481 }

482

483 asmlinkage void __init start_kernel(void)

484 {

485 char * command_line;

486 extern const struct kernel_param __start___param[], __stop___param[];

487

488 /*


현재 멈춰있는 곳의 소스 보기.

gdb 를 잘쓰면 아주 훌륭하다고 하는데... gdb 에 text ui 를 붙인 version 이 있는데 맨 아래에 추가적으로 소개하겠다.


5. simple rootfs 만들기.

  - get rootfs 

  http://downloads.yoctoproject.org/releases/yocto/yocto-1.2.1/machines/qemu/qemux86/ 이 링크에 접속하면

미리 만들어진 rootfs 가 있다. tar 로 묶여져있는 것과 ext3 확장자를 가진 것이 있는데, 지금 만들어진 image는 download 가 안된다. --;


일단 제일 작은 tar.gz로 된 rootfs 를 받자.

core-image-minimal-dev-qemux86.tar.bz2 이걸 받았다.

$ mkdir ~/work/rootfs_qemu

$ tar zxf core-image-minimal-dev-qemux86.tar.bz2 -C ~/work/rootfs_qemu


압축을 풀면, 아래와 같이 구성이 되어 있다.

bin  boot  dev  etc  home  lib  media  mnt  proc  sbin  sys  tmp  usr  var


이것을 qemu 가 mount 할 수 있도록 만들어 주면 된다. 이 방법도 eclipse CDT 에 참조했던 link 에 나와있다.

$ BLOCKS=$(((1024*$(du -m -s rootfs | awk '{print $1}')*12)/10))

$ genext2fs -z -d rootfs_qemu -b $BLOCKS -i 1024 rootfs.ext3

$ resize2fs rootfs.ext3 1G

$ tune2fs -j -c 0 -i 0 rootfs.ext3

사실 위의 command 들 모두가 뭐하는 것인지 자세히는 모른다. :)


암튼 만들어진 rootfs.ext3 로 mount 하여 기존 빌드한 kernel image로 부팅해보자.

qemu-system-x86_64 -no-kvm -kernel arch/x86/boot/bzImage -hda ~/work/rootfs.ext3 -append "root=/dev/sda console=ttyS0" -serial stdio 


부팅을 다하면,

.....

Starting system message bus: dbus.

Starting syslogd/klogd: done

Stopping Bootlog daemon: bootlogd.


Yocto (Built by Poky 7.0.1) 1.2.1 qemux86 ttyS0


qemux86 login: 


위에처럼 login 화면이 뜬다. root 라고 입력하면 shell 을 만날 수 있을 것이다.


여기서 kernel debugging을 하려면, qemu option 에 -s 만 옵션으로 넣고 gdb를  붙이면 된다. 이래저래 해보시길...


6. cgdb

검색을 해보면 아시겠지만, text based(gdb -tui) gdb 에 syntex highlight 를 넣은 것이다. 

$ sudo apt-get install cgdb


사용은, 

$ cgdb ./vmlinux

하고 위에서 remote 에 연결하는 command 를 하면 된다. break point를 잡고 run 했을 때, 어떻게 보이냐면,



위의 예제는

break point 를 compact_zone 함수에 걸어놓고, 

shell 에서 $ echo 1 > /proc/sys/vm/compact_memory 하면 그 함수가 불리고 break 가 걸린다.

그럼 source level 에서 쫓아갈 수 있을 것이다.


gdb 사용이 아직 익숙치 않아 봐야 할 부분들이 많다.



+ Recent posts