使用UML调试Linux内核和模块

http://linux.chinaitlab.com/kernel/835899.html

之前写过一些使用KGDB调试Linux内核和模块的文章,感觉使用KGDB+虚拟机的方式还是有些烦琐,而且需要占用较多的系统资源,每次调试启动Guest OS会花费较长时间,效率有些低。这里再总结一种使用User Mode Linux调试Linux内核的方法。User Mode Linux(简称UML)将Guest Linux OS作为用户进程运行,关于UML的更多优点可以查看UML官方网站。

  由于UML只是模拟的Linux系统,并利用Host OS本身的一些功能来完成实际的操作,因此对于底层的一些驱动(硬件相关的),UML无法获得其详细信息,所以UML无法调试。不过UML对于内核中硬件无关的代码的调试还是很有帮助的,比如文件系统部分,网络协议栈部分,内存管理和进程调度等等。因此UML对于研究Linux操作系统还是很有价值的。

  下面就简单地介绍下使用UML调试Linux内核和模块的初步知识。

  一、构建UML内核调试环境

  UML主要包括两个部分:可执行的操作系统镜像linux(vmlinux)和支持该操作系统执行的文件系统。除此之外,UML网站还提供一些辅助工具。

  如果你仅仅使用UML作为应用平台,而不是利用它进行内核方面的研究的话,你可以直接去UML的网站上下载构建好的二进制操作系统镜像,从而省掉冗长的编译过程。然而这里,我们需要使用UML来研究Linux内核,所以我们还是要构建一下UML内核树。

  1.构建UML内核树

  构建UML的内核树和构建普通的内核树类似,只是要注意UML用了一个模拟的ARCH,在编译内核时也要注意加上对应的选项(ARCH=um)。下面是构建UML内核的详细过程:

  1)下载一份内核源码包,本文还是以2.6.27.18为实验版本。新近的Linux源码包都内含了UM架构的部分,因此不需要像2.4版本的内核那样进行patch。

  2)解压之后cd到源码根目录下,编译之前先清理一下:

  make mrproper

  make mrproper ARCH=um

  3)配置内核并编译。默认配置的体系结构为um,配置完后编译内核

  make defconfig ARCH=um

  make gconfig ARCH=um

  make ARCH=um

  当编译完成时,会生成一个二进制文件linux。在这里要注意下面几点:

  a.make gconfig是使用gtk图形界面进行配置,因此需要安装辅助库,在Ubuntu下可用

  sudo apt-get install libglade2-dev命令进行安装。其他类型,如menuconfig等参考相关资料。

  b.在选择配置时确保选中了Kernel Hacking中的Compile the kernel with debug info(对应的DEBUG_INFO配置为yes)和Compile the kernel with frame pointers(对应的FRAME_POINTER配置为yes),这样生成的二进制内核linux中才有详细的调试信息。

  c.生成完.config文件后,编译之前,可以用编辑器打开源码根目录下的Makefile文件,把编译优化等级-O2调成-O1(见[3]),这样可以在保证编译通过的前提下生成的内核文件具有较好的调试效果。

  4)编译内核模块。编译um配置的内核模块,把它们放在mods目录(可以事先在源码根目录下建立mods目录,如果不存在改目录,则会自动创建)下。

  make modules_install INSTALL_MOD_PATH=mods ARCH=um

  生成的模块在mods/lib/modules/2.6.27.18/目录下,将这些生成的模块拷到UML的根文件系统的对应目录,可以去掉UML启动和停止时令人烦恼的错误,后面将会详细介绍。

  2.准备运行UML的根文件系统和交换文件系统

  1) UML的根文件系统是运行UML必须的,它类似于虚拟机的磁盘映像文件,所有UML操作都是在根文件系统上的进行的,UML运行时,必须指定一个根文件系统作为参数,否则UML无法启动。你可以自己动手制作根文件系统映像文件,其原理并不复杂:创建一个固定大小的文件(一般使用全零值作为内容填充),然后对其进行按照一定的文件系统进行格式化,再将其挂载到Host OS的某个目录下,然后从Host OS中拷贝系统启动时所必须的一些二进制及配置文件到根文件系统中。UML官网上也有关于如何制作根文件系统的介绍。自己制作根文件系统需要注意的一点就是要保证UML启动时所需的文件都被正确地拷贝到了根文件系统中。

  新手在自己制作根文件系统时可能会出现一些麻烦,如果文件拷少了,UML运行不起来,如果不管三七二十一,把Host OS的所有文件都拷进去,又浪费空间。所以如果嫌麻烦的话,可以直接到UML官网上下载自己喜欢的发行版本的根文件系统,作为映像文件来运行UML。需要注意的是,所使用的根文件系统类型一定要是UML内核所支持的类型,否则系统在启动时会出现下面的错误:

  Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(98,0)

  以上面编译的2.6.27.18的UML内核为例,默认的配置是不支持ext4文件系统的,如果你使用上面的内核映像,然后下载了最新版本的Ubuntu(Maverick Meerkat 10.10)的根文件系统(它是ext4类型的),那么在启动时就会出现上面的错误。因此,可以下载较老一些的Ubuntu(Hardy 8.04)的根文件系统。

  当下载或制作好更文件系统中后,可以将上一步生成的modules拷贝到UML根文件系统的对应目录中去。在Host Linux和UML根文件系统之间传递文件,个人认为最简单的方式就是使用挂载UML根文件系统到Host Linux中的目录。





之前写过一些使用KGDB调试Linux内核和模块的文章,感觉使用KGDB+虚拟机的方式还是有些烦琐,而且需要占用较多的系统资源,每次调试启动Guest OS会花费较长时间,效率有些低。这里再总结一种使用User Mode Linux调试Linux内核的方法。User Mode Linux(简称UML)将Guest Linux OS作为用户进程运行,关于UML的更多优点可以查看UML官方网站。

由于UML只是模拟的Linux系统,并利用Host OS本身的一些功能来完成实际的操作,因此对于底层的一些驱动(硬件相关的),UML无法获得其详细信息,所以UML无法调试。不过UML对于内核中硬件无关的代码的调试还是很有帮助的,比如文件系统部分,网络协议栈部分,内存管理和进程调度等等。因此UML对于研究Linux操作系统还是很有价值的。

下面就简单地介绍下使用UML调试Linux内核和模块的初步知识。

 

一、构建UML内核调试环境

         UML主要包括两个部分:可执行的操作系统镜像linux(vmlinux)和支持该操作系统执行的文件系统。除此之外,UML网站还提供一些辅助工具。

         如果你仅仅使用UML作为应用平台,而不是利用它进行内核方面的研究的话,你可以直接去UML的网站上下载构建好的二进制操作系统镜像,从而省掉冗长的编译过程。然而这里,我们需要使用UML来研究Linux内核,所以我们还是要构建一下UML内核树。

1.构建UML内核树

         构建UML的内核树和构建普通的内核树类似,只是要注意UML用了一个模拟的ARCH,在编译内核时也要注意加上对应的选项(ARCH=um)。下面是构建UML内核的详细过程:

         1)下载一份内核源码包,本文还是以2.6.27.18为实验版本。新近的Linux源码包都内含了UM架构的部分,因此不需要像2.4版本的内核那样进行patch。

         2)解压之后cd到源码根目录下,编译之前先清理一下:

make mrproper

make mrproper ARCH=um

         3)配置内核并编译。默认配置的体系结构为um,配置完后编译内核

make defconfig ARCH=um

make gconfig ARCH=um

make ARCH=um

         当编译完成时,会生成一个二进制文件linux。在这里要注意下面几点:

         a.make gconfig是使用gtk图形界面进行配置,因此需要安装辅助库,在Ubuntu下可用

sudo apt-get install libglade2-dev命令进行安装。其他类型,如menuconfig等参考相关资料。

         b.在选择配置时确保选中了Kernel Hacking中的Compile the kernel with debug info(对应的DEBUG_INFO配置为yes)和Compile the kernel with frame pointers(对应的FRAME_POINTER配置为yes),这样生成的二进制内核linux中才有详细的调试信息。

         c.生成完.config文件后,编译之前,可以用编辑器打开源码根目录下的Makefile文件,把编译优化等级-O2调成-O1(见[3]),这样可以在保证编译通过的前提下生成的内核文件具有较好的调试效果。

         4)编译内核模块。编译um配置的内核模块,把它们放在mods目录(可以事先在源码根目录下建立mods目录,如果不存在改目录,则会自动创建)下。

make modules_install INSTALL_MOD_PATH=mods ARCH=um

         生成的模块在mods/lib/modules/2.6.27.18/目录下,将这些生成的模块拷到UML的根文件系统的对应目录,可以去掉UML启动和停止时令人烦恼的错误,后面将会详细介绍。

2.准备运行UML的根文件系统和交换文件系统

         1) UML的根文件系统是运行UML必须的,它类似于虚拟机的磁盘映像文件,所有UML操作都是在根文件系统上的进行的,UML运行时,必须指定一个根文件系统作为参数,否则UML无法启动。你可以自己动手制作根文件系统映像文件,其原理并不复杂:创建一个固定大小的文件(一般使用全零值作为内容填充),然后对其进行按照一定的文件系统进行格式化,再将其挂载到Host OS的某个目录下,然后从Host OS中拷贝系统启动时所必须的一些二进制及配置文件到根文件系统中。UML官网上也有关于如何制作根文件系统的介绍。自己制作根文件系统需要注意的一点就是要保证UML启动时所需的文件都被正确地拷贝到了根文件系统中。

         新手在自己制作根文件系统时可能会出现一些麻烦,如果文件拷少了,UML运行不起来,如果不管三七二十一,把Host OS的所有文件都拷进去,又浪费空间。所以如果嫌麻烦的话,可以直接到UML官网上下载自己喜欢的发行版本的根文件系统,作为映像文件来运行UML。需要注意的是,所使用的根文件系统类型一定要是UML内核所支持的类型,否则系统在启动时会出现下面的错误:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(98,0)

         以上面编译的2.6.27.18的UML内核为例,默认的配置是不支持ext4文件系统的,如果你使用上面的内核映像,然后下载了最新版本的Ubuntu(Maverick Meerkat 10.10)的根文件系统(它是ext4类型的),那么在启动时就会出现上面的错误。因此,可以下载较老一些的Ubuntu(Hardy 8.04)的根文件系统。

         当下载或制作好更文件系统中后,可以将上一步生成的modules拷贝到UML根文件系统的对应目录中去。在Host Linux和UML根文件系统之间传递文件,个人认为最简单的方式就是使用挂载UML根文件系统到Host Linux中的目录,然后使用cp。拷贝Host Linux中源码根目录下mods中的文件到UML根文件系统中:

sudo cp –R mods/lib/modules/2.6.27.18 /mnt/lib/modules

Tips:

a.拷贝时确保目标目录存在

         b.有时拷贝会出现磁盘剩余空间不足的错误,可以试着先卸载然后重新挂载。

         拷贝完后在启动UML并成功运行后,在UML中执行下面的命令,更新模块相关文件:

UML# depmod -a

2) 对于交换文件系统,可以简单地使用下面的命令创建一个:

dd if=/dev/zero of=swap_fs bs=1M count=512

mkswap ./swap_fs

         然而实际上在试验的时候发现,一般情况下不使用交换文件系统也是可以的,个人感觉使用交换系统可能对系统的稳定性有些好处。

 

3.运行UML

         运行UML的命令如下:

./linux ubda=./Ubuntu-i386-root_fs mem=128M con=pts con0=fd:0,fd:1

./linux ubda=./Ubuntu-i386-root_fs ubdb=./swap_fs con=pts con0=fd:0,fd:1

Tips:在使用某些发行版本的根文件系统映像时,UML启动的时候可能会出现下面的错误:

xterm_open: $DISPLAY not set.

Failed to open console 6, err = -19

在UML运行命令后的con=pts con0=fd:0,fd:1参数可以忽略掉这些令人心烦的错误。

二、使用UML调试Linux内核

         调试UML的过程如下:

done@done-desktop:~/linux-2.6.27.18$ gdb ./linux

GNU gdb 6.8-debian

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

(gdb) break start_kernel

Breakpoint 1 at 0x8049408: file init/main.c, line 544.

(gdb) run ubda=./Ubuntu-i386-root_fs con=pts con0=fd:0,fd:1

Starting program: /home/done/linux-2.6.27.18/linux ubda=./Ubuntu-i386-root_fs con=pts con0=fd:0,fd:1

Locating the bottom of the address space ...

Program received signal SIGSEGV, Segmentation fault.

0x08066b0f in page_ok (page=<value optimized out>)

    at arch/um/os-Linux/sys-i386/task_size.c:31

31                      n = *address;

(gdb) handle SIGSEGV pass nostop noprint

Signal        Stop      Print   Pass to program Description

SIGSEGV       No        No      Yes             Segmentation fault

(gdb) c

Continuing.

0x10000

Locating the top of the address space ... 0xc0000000

Core dump limits :

        soft - 0

        hard - NONE

Checking that ptrace can change system call numbers...OK

Checking syscall emulation patch for ptrace...OK

Checking advanced syscall emulation patch for ptrace...OK

Checking for tmpfs mount on /dev/shm...OK

Checking PROT_EXEC mmap in /dev/shm/...OK

Checking for the skas3 patch in the host:

  - /proc/mm...not found: No such file or directory

  - PTRACE_FAULTINFO...not found

  - PTRACE_LDT...not found

UML running in SKAS0 mode

 

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

544             smp_setup_processor_id();

(gdb) l

539     asmlinkage void __init start_kernel(void)

540     {

541             char * command_line;

542             extern struct kernel_param __start___param[], __stop___param[];

543

544             smp_setup_processor_id();

545

546             /*

547              * Need to run as early as possible, to initialize the

548              * lockdep hash:

(gdb) p *command_line

$1 = -55 '▒'

(gdb)

 

Tips:

         在启动UML时可能会产生一个SIGSEGV信号异常,导致UML停止,无法继续执行,参考了国外UML论坛里的人提出了解决方法,就是不管它,在GDB中使用handle SIGSEGV pass nostop noprint命令跳过这个异常,目前暂不知道会对后面的调试造成什么样的影响。

 

三、使用UML调试Linux内核模块

         1. 编译ARCH为UML的可调式内核模块。需要在$(MAKE)后指明ARCH=um,否则编译时会报找不到sysdef.h头文件的错误,典型的Makefile如下:

obj-m := patch_vfs.o

KDIR :=/home/done/linux-2.6.27.18/

PWD :=$(shell pwd)

EXTRA_CFLAGS += -ggdb

 

module:

         $(MAKE) -C $(KDIR) M=$(PWD) ARCH=um modules

clean:

         ls | egrep -v "Makefile|patch_vfs.c" | xargs rm -rf

         2. 调试内核模块步骤,

 

done@done-desktop:~/linux-2.6.27.18$ gdb ./linux

GNU gdb 6.8-debian

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

(gdb) set-mod-break

set breakpoint in system module init function

Breakpoint 1 at 0x808d1df: file kernel/module.c, line 2288.

(gdb) run-uml

Locating the bottom of the address space ...

Program received signal SIGSEGV, Segmentation fault.

0x08066b0f in page_ok (page=<value optimized out>)

    at arch/um/os-Linux/sys-i386/task_size.c:31

31                      n = *address;

(gdb) ignore-sigsegv

0x10000

Locating the top of the address space ... 0xc0000000

Core dump limits :

        soft - 0

        hard - NONE

Checking that ptrace can change system call numbers...OK

Checking syscall emulation patch for ptrace...OK

Checking advanced syscall emulation patch for ptrace...OK

Checking for tmpfs mount on /dev/shm...OK

Checking PROT_EXEC mmap in /dev/shm/...OK

Checking for the skas3 patch in the host:

  - /proc/mm...not found: No such file or directory

  - PTRACE_FAULTINFO...not found

  - PTRACE_LDT...not found

UML running in SKAS0 mode

Linux version 2.6.27.18 (done@done-desktop) (gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)) #1 Mon Nov 22 13:05:26 CST 2010

Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 8128

Kernel command line: ubda=Ubuntu-i386-root_fs con=pts con0=fd:0,fd:1 root=98:0

PID hash table entries: 128 (order: 7, 512 bytes)

*

*Startup information, blablabla….

*

* Running local boot scripts (/etc/rc.local)                                     [ OK ]

 

Ubuntu 8.04.4 LTS (none) tty0

 

(none) login: root

Last login: Tue Nov 23 11:44:33 UTC 2010 on tty0

Linux (none) 2.6.27.18 #1 Mon Nov 22 13:05:26 CST 2010 i686

 

The programs included with the Ubuntu system are free software;

the exact distribution terms for each program are described in the

individual files in /usr/share/doc/*/copyright.

 

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by

applicable law.

 

To access official Ubuntu documentation, please visit:

http://help.ubuntu.com/

root@(none):~# ls

patch_vfs.ko

root@(none):~# insmod ./patch_vfs.ko

 

Breakpoint 1, sys_init_module (umod=0x804a018, len=72590,

    uargs=0x804a008 "\020Ph▒\022!\b▒ȡ\002") at kernel/module.c:2288

2288            if (mod->init != NULL)

(gdb) print-mod-segment

Name:.note.gnu.build-id Address:0xa813210

Name:.text Address:0xa813000

Name:.rodata.str1.1 Address:0xa813234

Name:.data Address:0xa8135cc

Name:.gnu.linkonce.this_module Address:0xa8135e0

Name:.bss Address:0xa813720

Name:.symtab Address:0xa813238

Name:.strtab Address:0xa8134b8

(gdb) add-symbol-file ~/program/kernel_program/patch_vfs/patch_vfs.ko 0xa813000 -s .data 0xa8135cc -s .bss 0xa813720

add symbol table from file "/home/done/program/kernel_program/patch_vfs/patch_vfs.ko" at

        .text_addr = 0xa813000

        .data_addr = 0xa8135cc

        .bss_addr = 0xa813720

(y or n) y

Reading symbols from /home/done/program/kernel_program/patch_vfs/patch_vfs.ko...done.

(gdb) break patch_vfs.c:48

 

Breakpoint 2 at 0xa81313b: file /home/done/program/kernel_program/patch_vfs/patch_vfs.c, line 48.

(gdb) c

Continuing.

root@(none):~# BUG: soft lockup - CPU#0 stuck for 129s! [swapper:0]

ls

 

Breakpoint 2, my_root_filldir (buf=0x9cc4f10, name=0x9d558be ".bashrc", nlen=7,

    off=23900456, ino=51188, x=0)

    at /home/done/program/kernel_program/patch_vfs/patch_vfs.c:48

48              if(!root_sb[current->pid % 1024])

(gdb) p name

$1 = 0x9d558be ".bashrc"

(gdb)

 

后记:该调试方法是在运行Linux操作系统之前设置断点,使用KGDB时是在操作系统完全起来后,使用Magic SysRq机制将执行权从Guest OS交给GDB,在使用UML调试时,发现在KGDB中的这种Magic SysRq并不能以相同的方式在UML的Guest OS中工作。即必须先设置好断点,然后再运行UML进行调试。个人觉得可能和UML的调试机制有关。使用UML进行内核调试,实际上Host OS还是把UML Guest OS看成普通进程,因此,和调试普通应用程序类似,也必须先设置断点(当然也可以程序跑起来后再通过PID来attach),再启动应用程序。也有可能是我的UML的Magic SysRq设置得不对,继续探索吧。

 

附~/.gdbinit配置文件:

define set-mod-break

        echo set breakpoint in system module init function\n

        break kernel/module.c:2288

end

 

define run-uml

        run ubda=Ubuntu-i386-root_fs con=pts con0=fd:0,fd:1

end

 

define ignore-sigsegv

        handle SIGSEGV pass nostop noprint

        continue

end

 

define print-mod-segment

        set $sect_num=mod->sect_attrs->nsections

        set $cur=0

        while $cur<$sect_num

                printf "Name:%-s Address:0x%x\n",mod->sect_attrs->attrs[$cur]->name,mod->sect_attrs->attrs[$cur]->address

                set $cur=$cur+1

        end

end

 

参考:

[1] David Frascone,《Debugging Kernel Modules with User Mode Linux》,Linux Journal,May 01, 2002

[2] UML官方网站http://user-mode-linux.sourceforge.net/

[3] 《VirtualBox下Ubuntu8.10的KGDB内核调试》http://hi.baidu.com/archiveman/blog/item/27971e538f1007060cf3e3f8.html

[4] 《linux 内核模块调试》http://blog.chinaunix.net/u3/95535/showart_2152446.html

 


你可能感兴趣的:(使用UML调试Linux内核和模块)