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 사용이 아직 익숙치 않아 봐야 할 부분들이 많다.



Kernel mailing list 활용 방법


예전 arm linux kernel 에 mailing list 에 subscribe 하여 email 로 patch 의 내용을 볼 수 있도록 하는 방법을 포스팅 한 적이 있다. (http://woodz.tistory.com/27


하지만 하루에도 너무 많은 내용의 patch 와 답글들이 난무하여 모두 보기엔 너무 많고, 골라보기엔 뭘 골라야 하는지도 몰라서 그냥 한달동안 받은 편지함에 쌓이는 메일을 방치하다 unsubscribe 를 하여 더이상 메일을 받지 않았다.


얼마전에 알게된 것인데, kernel mailing list 는 전체가 아닌 부분(part 별로)으로 mailing service 를 신청할 수 있는 방법이 있었다. 관심있는 것 한두개 정도만 등록해서 메일을 받아 보는 것이 효과적일 듯 하여 한번 신청을 해보았다.


두개를 등록했는데, 하루에 많으면 30개 정도 적으면 10개도 안되어 틈틈히 보면 좋을 것으로 보인다.


등록 절차는 간단하다.

1. http://vger.kernel.org/vger-lists.html 에 접속하여, 어떤 mailing list group 가 있는지 확인 한다.

   약 150개 group 이 있었다. group 별로 따로 설명은 없지만, group 이름만 봐도 어떤 내용인지 알수 있을 것이다.


2. 그 중에 일단 고른다. 저는 linux-mm(memory management) 를 선택했다.

아래 처럼 나와있다. 설명은 없지만, archives link 가 있으니 방문하여 어떤 patch 들이 들어오는지 확인 해보고 구독해도 될 것이다.

List: mm-commits;     ( subscribe / unsubscribe )
Info:
  
Archives:
	http://marc.info/?l=linux-mm-commits
	http://www.spinics.net/lists/mm-commits/
Footer:
  
--- 
To unsubscribe from this list: send the line "unsubscribe mm-commits" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

121 


3. subscribe 를 누르면 PC 의 emali client 가 구성해주지만, 없다면 gmail 이나 쓰는 메일에서 편지 쓰기하여 보낼 수 있다.

편지 쓰기를 하여, 


보내는 사람 : <your-email>@mail.com

받는 사람 : majordomo@vger.kernel.org

제목은 안씁니다.

내용 : subscribe mm-commits


위에 처럼 간단히 메일을 써서 보내면 됩니다. 물론 "subscribe mm-commits" 는 plan/text 로 보내야 하며, space 이외에 enter 등으로 분리하시면 안될 것입니다.


그러면 약 3분 뒤에,

Majordomo results 의 제목에 mail 이 옵니다. 메일을 정확히 보냈다면 이메일은 신경 안써도 됩니다.

(봇이 보내는 check mail 입니다. 다 읽어 보지도 않았습니다. ㅎㅎ)

그리고 조금 더 뒤에 아래와 같이 확인 메일이 옵니다.
(확인이 되어야 가입이 됩니다.)

--

Someone (possibly you) has requested that your email address be added
to or deleted from the mailing list "mm-commits@vger.kernel.org".

If you really want this action to be taken, please send the following
commands (exactly as shown) back to "Majordomo@vger.kernel.org":

        auth abcdefg subscribe mm-commits your-email@mail.com

If you do not want this action to be taken, simply ignore this message
and the request will be disregarded.

If your mailer will not allow you to send the entire command as a single
line, you may split it using backslashes, like so:

        auth abcdefg subscribe mm-commits \
        your-email@mail.com

If you have any questions about the policy of the list owner, please
contact "mm-commits-approval@vger.kernel.org".

Thanks!


위의 메일을 받게 된다면 다시한번 majordomo@vger.kernel.org 에 메일을 보내야 한다.

메일은,
받는 사람 : majordomo@vger.kernel.org
보내는 사람 : your-email@mail.com
제목은 비워두시면 됩니다.
내용 :
auth abcdefg subscribe mm-commits your-email@mail.com

제가 잘보이라고 빨간색으로 한겁니다. auth  에서 시작해서 맨 한 줄을 메일 내용에 복사 & 붙여넣기 하면 됩니다.
이것또한 줄바꿈없이 복사 만 딱해서 보내시면 됩니다. 아래의 줄바꿈을 하려면 역슬래쉬(\) 를 넣어주세요 하는데 그냥 한줄로 보내자.

위에 처럼 보내면 약 5분내로 가입 메세지를 보내주고 이제 메일링 서비스를 받을 수 있습니다.

Welcome to mm-commits


Welcome to the mm-commits mailing list!

Please save this message for future reference.  Thank you.

If you ever want to remove yourself from this mailing list,
you can send mail to <Majordomo@vger.kernel.org> with the following
command in the body of your email message:

    unsubscribe mm-commits

or from another account, besides your-email@mail.com:

    unsubscribe mm-commits your-email@mail.com

If you ever need to get in contact with the owner of the list,
(if you have trouble unsubscribing, or have questions about the
list itself) send email to <owner-mm-commits@vger.kernel.org> .
This is the general rule for most mailing lists when you need
to contact a human.

 Here's the general information for the list you've subscribed to,
 in case you don't already have it:

Archives:
        http://marc.info/?l=linux-mm-commits


이제 틈틈히 올라오는 patch 들을 확인 할 수 있습니다. 저는 linux-mm 과 linux-janitor 두가지만 했습니다. ^^



'Development Tip' 카테고리의 다른 글

const char* vs. char const*  (0) 2014.02.19
Fish shell environment  (0) 2013.12.03
PuTTY 설정 값 공유하는 방법  (0) 2013.10.07
Git with eclipse  (0) 2013.04.04
Tips on Linux  (0) 2013.03.11

How does get_current() work?


Kernel 을 보다 보면, "current"를 사용한 코드들이 보인다. 이에 관련해서 어떻게 현재 수행중인 process의 task struct 를 갖고 올 수 있는지 확인해 보자.


참고 url : http://kernelnewbies.org/FAQ/get_current

             http://kernelnewbies.org/FAQ/current


위의 두 가지를 참고하여 재구성 해봤다. 


일단 process 마다 virtual address space 를 갖고 있다. 이는 User/Kernel 의 영역이 나뉘어져 있는데(대게 3G(user)/1G(kernel) 을 많이 사용한다), 이중 kernel 영역에 get_current() 를 통해 현재 process 정보를 얻어올 수 있도록 하는 것이 get_current() 이다.


Kernel 영역의 어디에 이와 같은 정보가 저장되느냐면, Kernel 이 갖고 있는 stack 의 가장 아래쪽에 저장되어 있다.(stack 이 높은 주소에서 낮은 주소 방향으로 이동하며 사용되니까 가장 아래쪽이라 하면.. 아래 그림을 보자)



대게 32bit 에서는 8KB stack 을 사용한다고 한다. 위에서 처럼 stack 의 맨 아래의 정보를 얻기 위한 macro가 get_current() 이다.


일단 get_current() 는 thread_info struct 정보를 넘기고 thread_info struct 는(architecture 마다 다른 듯 함.)

struct thread_info {

unsigned long flags; /* low level flags */

int preempt_count; /* 0 => preemptable, <0 => bug */

mm_segment_t addr_limit; /* address limit */

struct task_struct *task; /* main task structure */

struct exec_domain *exec_domain; /* execution domain */

__u32 cpu; /* cpu */

__u32 cpu_domain; /* cpu domain */

struct cpu_context_save cpu_context; /* cpu context */

__u32 syscall; /* syscall number */

__u8 used_cp[16]; /* thread used copro */

unsigned long tp_value;

struct crunch_state crunchstate;

union fp_state fpstate __attribute__((aligned(8)));

union vfp_state vfpstate;

#ifdef CONFIG_ARM_THUMBEE

unsigned long thumbee_state; /* ThumbEE Handler Base register */

#endif

struct restart_block restart_block;

};


get_current() 로 얻은 thread_info 를 통해 현재 process의 상태 및 task_struct 도 갖고 올 수 있다.


Kernel Stack 은 항상 고정된 주소이고 이 thread info가 저장되는 위치는 항상 같은 곳이다. 그래서 얻어오는 과정의 소스를 분석해보자.

//  code 에서 current->flag |= ... 이라고 사용하면

// current macro는

// include/asm-generic/current.h

#define get_current() (current_thread_info()->task)

#define current get_current()


간단하다. 위에서 처럼 current->XXX 로 사용하면 된다.(thread_info 구조체에 있는 녀석으로)


current_thread_info() 의 함수는 architecture 마다 따로 구현이 되어 있다. ARM 것을 보면,

// arch/arm/include/asm/thread_info.h

static inline struct thread_info *current_thread_info(void)

{

register unsigned long sp asm ("sp");

return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));

}


단지 현재 stack pointer register를 읽어다가, sp & ~(THREAD_SIZE - 1) 했다.

현재 32bit 의 ARM 에서 stack 의 크기는 8KB 이다. 그렇다면 stack 의 12bit 만 0으로 만들어 주면 제일 낮은 주소의 stack 주소가 나오고 거기에는 현재 thread_info 의 address가 있다는 것이다.(현재 stack pointer 가 위의 그림에서 중간쯤에 위치 하더라도 하위 12bit 만 0으로 해주면 맨 아래의 thread_info 를 접근 할 수 있을 것이다.)


이렇게 해서 current macro를 이용해서 thread_info 구조체의 내용을 갖고와 process 의 status 확인 및 control 이 가능 한것이다.


+ Recent posts