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

  • 相关阅读:
    复用$.ajax方式传递参数错误处理
    JS传递函数并且调用
    JQuery $.axaj的基本格式
    AOP的具体实践-简化结果返回的处理
    将Python打包成可执行文件exe的心路历程
    通过Python实现一个文档的半自动录入工具
    我的第一次实习感悟
    docker nginx+php-fpm+mysql
    使用Harbor搭建docker私服
    python 验证码获取后处理降噪、灰度、保存
  • 原文地址:https://www.cnblogs.com/Alexkk/p/11997737.html
Copyright © 2011-2022 走看看