zoukankan      html  css  js  c++  java
  • 构建调试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!
    ");
       }
       else
       {
           /*  parent process   */
           printf("Please input hello...
    ");
       }
    }
    
    

    Replyhi

    int Replyhi()
    {
       char szBuf[MAX_BUF_LEN] = "";
       char szReplyMsg[MAX_BUF_LEN] = "hi";
       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

  • 相关阅读:
    hdu 5007 水题 (2014西安网赛A题)
    hdu 1698 线段树(成段替换 区间求和)
    poj 3468 线段树 成段增减 区间求和
    hdu 2795 公告板 (单点最值)
    UVaLive 6833 Miscalculation (表达式计算)
    UVaLive 6832 Bit String Reordering (模拟)
    CodeForces 124C Prime Permutation (数论+贪心)
    SPOJ BALNUM (数位DP)
    CodeForces 628D Magic Numbers (数位DP)
    POJ 3252 Round Numbers (数位DP)
  • 原文地址:https://www.cnblogs.com/Alexkk/p/11997737.html
Copyright © 2011-2022 走看看