编译内核

一、从官网上下载源码,解压。

建议下载的内核与本机内核版本一致,这样可以直接使用本机的配置文件/boot/config-{version_name}进行编译。{version_name}字符串与本机的linux内核版本相关,如/boot/config-5.4.0

二、编译之前,首先安装编译需要的依赖包

ubuntu下:

1
sudo apt install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison pkg-config dwarves

三、进入下载的源码根目录,并将本机的配置信息拷贝到此处,命名为.config

1
cp -v /boot/config-`uname -r` .config

使用这种方式可以沿用本机很多适合的配置,不必在接下来的配置过程中去处理过多不熟悉的配置项。

四、额外配置

配置文件中的额外配置

1
make menuconfig

该命令会开启一个用户界面,允许重新设定配置项并写入.config文件中。

  1. 如果需要编译出的内核能够调试,一般需要设置这些配置项(如果是从本机复制的.config,这些项默认都是配置好的,不用再手动设置,可以在编译前确认一遍):
  • CONFIG_KGDB
  • CONFIG_KGDB_SERIAL_CONSOLE:使KGDB通过串口与主机通信(打开这个选项,默认会打开CONFIG_CONSOLE_POLL和CONFIG_MAGIC_SYSRQ)
  • CONFIG_KGDB_KDB
  • CONFIG_DEBUG_KERNEL
  • CONFIG_DEBUG_INFO
  • CONFIG_FRAME_POINTER
  • 注释CONFIG_DEBUG_RODATA:使某些架构下,只读区域可增加断点
  • 注释CONFIG_CC_OPTIMIZE_FOR_SIZE,否则使用-Os
  • CONFIG_KALLSYMS
  • CONFIG_DEBUG_SECTION_MISMATCH:去掉inline优化(此条存疑,不确定功能是否为去掉inline,且设置该项为y后编译不通过)
  1. 一般会默认打开的一个配置项 CONFIG_RANDOMIZE_BASE,此处手动把它注释,禁止地址随机化。

  2. 如果有网口连接KGDB的需求,打开这些配置项

  • CONFIG_NETCONSOLE (Networking support -> Network console logging support)

  • CONFIG_KGDB_ETH (Kernel hacking -> KGDB -> Method of KGDB communication -> Ethernet)

Makefile的额外配置

修改源码根目录的Makefile文件,将其中的-O2全部改为-O1,尽可能方便调试。(不能改成-O0,否则编译过不了)

五、编译与安装

  1. 在源码根目录下运行make命令进行编译。
  2. 编译完成后,运行sudo make modules_install安装以模块方式编译的内核。
  3. 运行sudo make install安装内核镜像。该命令会自动生成新内核的initramfs/initrd,并更新grub中的多内核引导。
  4. 修改/etc/default/grub文件,将其中的GRUB_TIMEOUT_STYLE=hidden注释掉,并修改GRUB_TIMEOUT = 10,以流出10秒时间让用户选择进入哪个内核。完成后,执行update-grub命令并重启系统。
  5. 确认新内核安装成功后,可以进入源码目录执行make clean,删除编译时产生的中间文件。

六、可能遇到的其它问题

  • 编译时报错:debian/certs/debian-uefi-certs.pem,由certs/x509_certificate_list需求停止。或:No rule to make target 'debian/canonical-certs.pem
    在.config中,找到CONFIG_SYSTEM_TRUSTED_KEYS,将其设置为空,即CONFIG_SYSTEM_TRUSTED_KEYS="";或在源码根目录运行下面两条命令:

    1
    2
    scripts/config --disable SYSTEM_TRUSTED_KEYS
    scripts/config --disable SYSTEM_REVOCATION_KEYS

    然后重新编译。

  • 编译时报错:failed: load btf from vmlinux: invalid argument.可能是pahole版本过高,BTF格式不兼容导致的,将该软件包降级即可。

    1
    2
    $ sudo apt-cache policy pahole
    $ sudo apt install pahole=1.22-8

调试内核

准备步骤

  1. 进行编译的机器为目标机,还需要再开一台虚拟机做开发机,两台机器通过串口连接,才能调试内核。

  2. 开发机只需要解压的源码(无需编译),以及目标机上编译出的vmlinux文件(将其放到开发机源码目录)。

  3. 配置两台虚拟机之间的串口。

  • 开发机

  • 目标机

    与开发机相同,创建串口,把命名管道下的“客户端”改成“服务器端”即可。

  1. 进行串口测试:目标机执行#cat /dev/ttyS1,开发机执行#echo "test" > /dev/ttyS1。如果目标机上能看到test输出,则串口配置OK。

  2. 配置目标机的grub.cfg

修改/etc/default/grub文件,增加一行:

GRUB_CMDLINE_LINUX="nokaslr rootdelay=90quiet splash text kgdboc=ttyS1,115200"

其中,115200 为串口波特率,nokaslr禁止了内核地址随机化(如果配置时已经设置了CONFIG_RANDOMIZE_BASE,则这里可以不用添加nokaslr)

调试方法

  • 目标机

    执行echo ttyS0 > /sys/module/kgdboc/parameters/kgdbocecho g > /proc/sysrq-trigger,控制交给kgdb,目标机进入假死状态,等待开发机的gdb连接。

  • 开发机

    进入内核源码目录,gdb ./vmlinux启动gdb。

    gdb配置项:

    1. 设置串口波特率,此处设置与目标机grub中相同的值。

    set serial baud 115200

    1. 通过串口连接kgdb

    target remote /dev/ttyS1

    可以在源码目录下新建一个.gdbinit文件,文件中写入以上两项。gdb启动时,会读取工作目录下的.gdbinit文件并运行其中的命令,这样,就不必在每次启动gdb时都进行配置。

开发机中运行continue,使目标机得以继续运行,直到命中断点,或显式地执行命令将控制交给kgdb,然后开发机能够继续调试。