構建調試Linux內核(32位)網絡代碼的環境MenuOS系統


構建調試Linux內核(32位)網絡代碼的環境MenuOS系統

最后的目錄:

說明:qemu qemu-system-i386 qemu-system-x86_64
qemu-system-i386是32位的QEMU的命令
qemu-system-x86_64是64位的QEMU的命令
qemu 是軟鏈接到qemu-system-i386,二者是一樣的,如果qemu沒有軟鏈接,是無法執行的。

以下過程是回憶所寫,有些小細節可能記錯了,部分命令是手敲的,不一定對,僅供參考。

安裝,編譯linux內核

步驟 1:下載,配置編譯為32位

#如果想編譯為64位,請直接忽略此步驟最后一條命令,接步驟二開始,但是后面需要更改一些qemu命令的格式,要都按照64位來做,后面我大概提一下,但是具體細節我沒做,所以有什么坑我也不知道。

mkdir LinuxKernel  #創建一個項目目錄
cd LinuxKernel
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz  #下載linux-5.0.1的內核,當然也可以下載其他版本的,就是有點慢。
xz -d linux-5.0.1.tar.xz    #解壓
tar -xvf linux-5.0.1.tar
cd linux-5.0.1
sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev #安裝內核編譯所需的庫
make i386_defconfig  #生成32位x86的配置文件

步驟 2:配置編譯需要debug信息

#針對32位的,步驟二可以在這步做,也可以在后面gdb調試那一步之前做,我當時是在那一步之前做的,但是現在想想沒必要編譯兩次,因為編譯真的需要很久,64位就在這兒做吧。

make menuconfig

#執行make menuconfig之后,會跳出一個圖形化界面,就在圖形化界面中完成以下操作,如果沒有跳出,或者報錯,自行解決界面大小適應問題:安裝vmware tool,或者在設置中調整分辨率。

1:選擇 Kernel hacking
2:選擇 Compile-time checks and compiler options
3:選擇 [ ]Compile the kernel with debug info 
4:按Y  前面就多了一個 [*] Compile the kernel with debug info 
5:選擇 save
6:按 esc,直到退出圖形化界面

步驟 3:編譯

make

漫長的等待開始了,直到編譯完成。

步驟 4:升級內核

#可以忽略此步驟!!!!因為這個步驟是老師上課講的的,但是我做的時候,機子在reboot的時候總是錯,所以后面就跳過了。
#歡迎大佬指出問題

uname -a
sudo make modules_install  # ⚠️安裝前通過系統快照備份系統,以防出現故障前功盡棄
sudo make install
sudo update-grub
reboot
uname -a

制作根文件系統

步驟 1:QEMU虛擬機加載內核

cd ~/LinuxKernel/
sudo apt install qemu  # 安裝qemu命令
qemu-system-i386 -kernel  linux-5.0.1/arch/x86/boot/bzImage #qemu虛擬機加載 linux-5.0.1內核,這條命令可以不用執行,因為后面構造menuOS的makefile中是包含了這條命令的

步驟 2: 構造MenuOS

#下載menu系統,並在LinuxKernel目錄下建一個子目錄rootfs,當作menuOS的根目錄

git clone https://github.com/mengning/menu.git

mkdir rootfs

步驟2.1: 安裝libc6-dev-i386和修改Makefile

安裝libc6-dev-i386

sudo apt-get install libc6-dev-i386

修改makefile,我的做法是方式二

方式一

cd menu

vim Makefile

qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img  #修改前

qemu-system-i386 -kernel  ../linux-5.0.1/arch/x86/boot/bzImage  -initrd ../rootfs.img #修改后

wq

64位的就修改為 qemu-system-x86_64 -kernel ../linux-5.0.1/arch/x86_64/boot/bzImage -initrd ../rootfs.img

方式二

#如果不想使用qemu-system-i386,仍然想使用qemu命令,就改為如下,然后執行一個軟鏈接

cd menu

vim Makefile

qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img  #修改前

qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img #修改后

wq

sudo ln -s /usr/bin/qemu-system-i386  /usr/bin/qemu

步驟2.2 初始化根目錄

linux啟動后期會在根⽬錄中尋找⼀個應⽤程序來運⾏,在根⽬錄下提供init是⼀種可選⽅案

#在menu目錄下執行一下命令
make rootfs

結果應該是這樣

回車,然后執行help命令查看當前構建的menuOS系統中的命令 ,其他命令都可以,但是quit命令無效,hh。

gdb 調試

在執行gdb 調試之前,保證make menuconfig那個步驟已經執行,不然編譯的內核系統不含調試信息。

步驟 1:啟動gdb server

1 關閉 之前打開的menuOS系統界面
2 執行 qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img -append  nokaslr -s -S

為什么和老師的不一樣?em 我也不知道為什么,可能teacher給的命令只適合teache的機子,反正我又是一堆錯,這兒寫的命令也可能不適合你的機子。
所以多提供兩條參考命令,反正我的機子是不行的,說不定你的機子行呢,如下

qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img  -s -S   #(我的機子執行之后調試停不下來)
(32)qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -hda rootfs.img -append "root=/dev/sda init=/init nokaslr" -s -S
(64)qemu-system-x86_64 -kernel ../linux-5.0.1/arch/x86_64/boot/bzImage -hda rootfs.img -append "root=/dev/sda init=/init nokaslr" -s -S
#(調試的時候,中途報錯:VFS,unable to mount rootfs on unkwon-block(x,x))

步驟2 gdb客戶端連接gdb server

#打開另一個終端

gdb
file ~/LinuxKernel/linux-5.0.1/vmlinux
break start_kernel
target remote:1234
c
list

如圖

多打幾個斷點看看,內核啟動的過程,具體細節再研究研究,看我后續部分

構建MenuOS的網絡功能

參考老師的實驗樓:https://www.shiyanlou.com/courses/1198

步驟1: 將 TCP 網絡通信程序的服務端集成到 MenuOS 系統中

cd ~/LinuxKernel  
git clone https://github.com/mengning/linuxnet.git
cd linuxnet/lab2
make
cd ../../menu/
make rootfs #改一下Makefile

步驟2: 將 TCP 網絡通信程序的客戶端集成到 MenuOS 系統中

cd ~/LinuxKernel  
git clone https://github.com/mengning/linuxnet.git
cd linuxnet/lab3
make rootfs  #報錯之后,修改Makefile

結果如圖:menuOS下面已經多了replyhi,和 hello命令,后面再看細節。

后續。。。

linux 內核的啟動過程

start_kernel 部分代碼

asmlinkage __visible void __init start_kernel(void)
{
  char *command_line;
  char *after_dashes;

   set_task_stack_end_magic(&init_task); #設置0號進程的棧邊界。可以通過 gdb 調試查看  p init_task->pid  看到它的進程號
   smp_setup_processor_id();
   debug_objects_early_init();

   cgroup_init_early();

   local_irq_disable();
   early_boot_irqs_disabled = true;

   /*
    * Interrupts are still disabled. Do necessary setups, then
    * enable them.  #設置中斷表
    */
   boot_cpu_init();
   page_address_init(); #初始化虛擬頁地址
   pr_notice("%s", linux_banner);
   setup_arch(&command_line);
   /*
   * Set up the the initial canary and entropy after arch
    * and after adding latent and command line entropy.
    */
   add_latent_entropy();
   add_device_randomness(command_line, strlen(command_line));
   boot_init_stack_canary();
   mm_init_cpumask(&init_mm);
   setup_command_line(command_line);
   setup_nr_cpu_ids();
   setup_per_cpu_areas();
   smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
   boot_cpu_hotplug_init();

start_kernel 代碼分析:參考博客:https://www.cnblogs.com/yjf512/p/5999532.html
說明:init_task 它是誰?他是0號進程,本質是一個結構體task_strcuk,即通用的進程描述符,它在哪創建?在cpu_startup_entry處創建,參考:https://www.cnblogs.com/dakewei/p/11558027.html
結構體task_strcuk是什么,參考:https://blog.csdn.net/qq_25424545/article/details/80289683
0號進程是如何調用rest_init創建1,2號進程 的,以及他們之間的關系?參考:https://www.cnblogs.com/alantu2018/p/8526970.html

如何往menu系統中添加tcp通訊功能的

以linuxnet/lab3為例

main.c

步驟 1 重啟網卡

BringUpNetInterface

為什么呢?為了配置網卡協議,以及綁定主機ip。

步驟2 構造命令和對應的handle函數

MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi);

MenuConfig中主要是設置一個結構體,將cmd和handle關聯起來
例如在menu中執行replyhi,就會調用相應的函數StartReplyhi,StartReplyhi中子進程負責調用replyhi,replyhi就是執行tcp服務器的代碼。

StartReplyhi

int StartReplyhi(int argc, char *argv[])
{
   int pid;
   /* fork another process */
   pid = fork();
   if (pid < 0)
   {
       /* error occurred */
       fprintf(stderr, "Fork Failed!");
       exit(-1);
   }
   else if (pid == 0)
   {
       /*   child process  */
       Replyhi();
       printf("Reply hi TCP Service Started!\n");
   }
   else
   {
       /*  parent process   */
       printf("Please input hello...\n");
   }
}

Replyhi

int Replyhi()
{
   char szBuf[MAX_BUF_LEN] = "\0";
   char szReplyMsg[MAX_BUF_LEN] = "hi\0";
   InitializeService();
   while (1)
   {
       ServiceStart();#宏定義  包括socket,bind,listen,accpet等函數
       RecvMsg(szBuf);
       SendMsg(szReplyMsg); #宏定義,就是調用recv函數
       ServiceStop();
   }
   ShutdownService();#宏定義,就是調用close函數
   return 0;
}

socket,bind,listen,accpet這些函數對應着以下系統調用

switch (call) {
   case SYS_SOCKET:
       err = __sys_socket(a0, a1, a[2]);
       break;
   case SYS_BIND:
       err = __sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
       break;
   case SYS_CONNECT:
       err = __sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
       break;
   case SYS_LISTEN:
       err = __sys_listen(a0, a1);
       break;
   case SYS_ACCEPT:
       err = __sys_accept4(a0, (struct sockaddr __user *)a1,
                   (int __user *)a[2], 0);
       break;
   case SYS_GETSOCKNAME:
       err =
           __sys_getsockname(a0, (struct sockaddr __user *)a1,
                     (int __user *)a[2]);
       break;
   case SYS_GETPEERNAME:
...

進程和系統調用之間是怎么進行的?
進程可以跳轉到的內核中的位置叫做system_call。在此位置的過程檢查系統調用號,它將告訴內核進程請求的服務是什么。然后,它再查找系統調用表sys_call_table,找到希望調用的內核函數的地址,並調用此函數。
gdb 調試lab3的看一看效果

gdb
file vmlinux
break sys_socketcall
target remote:1234
c
list
list
n
n
s 進入__sys_socket
bt  #查看堆棧

輸出如下

(gdb) bt
#0  __sys_socket (family=2, type=2, protocol=0) at net/socket.c:1327  #sys_socket 的內容主要就是上面的switch中的結果了
#1  0xc1757b98 in __do_sys_socketcall (args=<optimized out>,
   call=<optimized out>) at net/socket.c:2555
#2  __se_sys_socketcall (call=1, args=-1076677360) at net/socket.c:2527  #找到了對應socket類的系統調用函數總入口地址
#3  0xc1002095 in do_syscall_32_irqs_on (regs=<optimized out>)  #syscall_trace_enter取出系統調用號 nr;到sys_call_table中去找到nr號對應的系統調用服務程序去執行后返回值放入ax。
   at arch/x86/entry/common.c:334
#4  do_fast_syscall_32 (regs=0xc7895fb4) at arch/x86/entry/common.c:397    #做一些額外的設置,里面可是有0x80,然后調用do_syscall_32_irqs_on
#5  0xc199141b in entry_SYSENTER_32 () at arch/x86/entry/entry_32.S:887  #保存現場將相關寄存器中的值壓棧(rax,rsi,rdi等)
#6  0x00000001 in ?? () #上層就是用戶進程了
#7  0xbfd33510 in ?? ()


系統調用咋回事?參考:https://docs.huihoo.com/joyfire.net/6-1.html
更多socke函數的分析 參考: https://blog.csdn.net/zhangskd/category_9263957.html


免責聲明!

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



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