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,並且不能再往下走,到此內核的初步調試結束:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM