linux内核编译与调试方法

casualet + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”  

前言:

很多人都会比较好奇操作系统是怎么工作的,但是由于系统庞大缺乏工具, 往往导致无从下手。本文将结合linux内核3.18.6的部分代码, 讲述利用虚拟机和gdb进行调试的过程,从而帮助理解操作系统的原理。

我们知道,linux操作系统的启动流程大致如下:

首先,我们有一个cpu和磁盘中的一些操作系统代码,开机加电的时候,由于指令必须要先放到内存中才可以被执行,而这个时候操作系统本身还在磁盘里,不能做任何操作, 所以需要BIOS程序来完成操作系统的搬运工作。操作系统代码放置到内存中的固定位置以后,就可以开始执行操作系统的代码,进行一些初始化的工作。这个时候, 操作系统还没有进程的概念。初始化的开始,执行的还是汇编的代码。

然后,我们进入了init/main.c 中的start_kernel函数,从这里开始,我们还是需要进行一系列的初始化,到这里都是c代码了。start_kernel的最后一行代码是调用rest_init函数,在这个函数中会调用kernel_init函数,这个函数会形成进程1.  而从start_kernel=>rest_init执行下去,最后会进入一个whlie(1)循环,在循环内部进行一些操作, 这个就是进程0,最后进入一个idle的状态.

对于上面说的进行1,会进行如下的操作: 它会搜索根文件系统下的一些目录,找一个init文件来执行。而这个文件的执行需要一些配置文件,通过这个配置文件,我们可以执行决定系统启动等级等操作。这个进程1(init)是第一个用户态的进程,在kernel_init之前都是内核初始化,这之后则是用户态的初始化,所以这是一个分界点。

 

我们将使用qemu工具以及gdb调试工具对上面的原理进行一些实际操作, 加强理解。

1. 首先是下载源代码以及解压
首先在home下建立一个新的文件夹LinuxKernel cd ~/LinuxKernel/ wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz xz -d linux-3.18.6.tar.xz tar -xvf linux-3.18.6.tar cd linux-3.18.6 2.然后开始编译 make i386_defconfig(完成以后会显示configuration written to .config,也就是把配置信息写到.config文件中了) make
这样经过一个比较长的时间, 就编译完成了。 ====================================================================================================================================================================================================================================== 接下来开始制作根文件系统: cd ~/LinuxKernel/ mkdir rootfs git clone https://github.com/mengning/menu.git(这个是上课老师提供的材料) cd menu gcc -o init linktable.c menu.c test.c -m32 -static –lpthread (在这一步的时候,出现了类似 fatal error: sys/cdefs.h: No such file or directory 的错误提示,搜索以后发现在ubuntu amd64下,需要下载一个包,下载的命令是:apt-get install libc6-dev-i386, 安装gcc-multilib 和g++-multilib 也是可以的
具体参考http://askubuntu.com/questions/470796/fatal-error-sys-cdefs-h-no-such-file-or-directory) cd ../rootfs cp ../menu/init ./ find . | cpio -o -H newc |gzip -9 > ../rootfs.img (这一步执行完毕以后,显示1868 blocks) 并且呈现了如下的目录文件.

可以看到,第一是linux内核源码,第二个menu是制作根文件系统的时候下载的,我们的最后一个命令产生了一个rootfs.img

然后使用下面的命令就可以启动操作系统了,如果没有安装qemu需要先安装。

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

上面的步骤让我们可以先把内核跑起来,但是要进行内核的调试, 上面这种编译是不行的,我们需要重新编译,加入调试信息,具体步骤如下:

首先进入源码的目录, make menuconfig, 发现提示错误:fatal error: curses.h: No such file or directory 所以按照网上的教程有安装了两个包,命令是: apt-get install libncurses5-dev libncursesw5-dev
然后make menuconfig 成功,会出现如下的目录:

我们选择kernel hacking选项,进入以后继续选择compile-time checks and compile options, 然后选择compile the kernel with debug info

我们选择save, 就可以吧这个信息保存到.config中, 然后重新输入make 进行编译就可以了。

 

我们已经编译好了内核, 并且带了调试信息, 就下来可以开始进行内核的调试了。 我们输入以下命令:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

这个命令使得我们可以通过tcp的1234端口连接gdb进行调试。 所以我们打开另外一个窗口, 进入~/LinuxKernel 目录,然后输入gdb回车。

接下来输入file linux-3.18.6/vmlinux 加载文件, 下面的图片可以看出,在源码文件夹中是有这个vmlinux文件的。

然后输入target remote:1234 连接gdb

如果上述步骤都很成功,就可以开始设置断点进行调试了, 下面通过图片文字代码展示一个简单的调试过程。

 

首先,我们打开内核源码下的init目录下的main.c文件,可以看到里面有start_kernel函数,这个是操作系统的入口。

所以我们输入两个命令 break start_kernel 以及 break rest_init, 用gdb设置了两个断点. 其中rest_init 是start_kernel函数的最后一行,调用rest_init函数会启动进程1.

 

我们看start_kernel函数的内容,就会发现, 里面很多的init函数,这些都是用来做初始化工作的。我们先输入c,运行到第一个断点start_kernel,可以看到左边的窗口有如下的变化:

我们输入backtrace 查看调用的栈,可以发现start_kernel<=head32.c:49

然后我们继续运行到rest_init可以看到窗口有如下的变化:

可以看到, 我们完成了start_kernel中大部分的初始化函数, 所以窗口会输出很多的提示信息。然后我们的第二个断点使得我们停在了rest_init函数中。 使用backtrace, 我们可以看到这个函数是由start_kernel函数调用的, 这个和我们的预期符合。

 

我们可以看一部分rest_init的代码:

我们使用step进入上面显示的函数,rcu_scheduler_starting,可以查看结果:

同样,使用backtrace,我们可以看到rcu_scheduler_starting是有rest_init来调用的。对于上述的第403行代码,kernel_thread(kernel_init,NULL,CLONE_FS),会调用kernel_init函数,启动进行1,做的工作是在根文件系统目录下找特定的文件来执行,

其部分代码如下:也就是在四个目录下搜索init来进行执行。我们在自己的系统上测试,发现确实是可以找到这个文件的。

 

所以我们使用next命令,不断执行rest_init中的语句:

执行到最后,我们从左边的窗口可以看出来,内核启动起来了。

 

 

 

我们看到,执行到了rest_init的最后一行的内容,cpu_startup_entry. 这个是在 kernel/shed/idle.c 里面的一个函数。

继续执行下去,我们发现进入了cpu_idle_loop,并且不能再往下走,到此内核的初步调试结束:

 

 

posted @ 2016-03-13 11:31  Casualet  阅读(14418)  评论(0编辑  收藏  举报