编辑推荐:推荐使用vscode单步调试运行内核。在按F5单步之前,请先设置断点,比如在start_kernel函数中设置断点,直接在源码左侧点击左键即可。以下文章来自谈之马通。笔者探知神通按照上一篇介绍VSCode的文章搭建了纯Win10下的LinuxKernel单步调试IDE环境。本文继续介绍纯Ubuntu下用VSCode搭建的LinuxKernel单步调试IDE环境。0.环境介绍0.0主机版本主机:Ubuntu(任何其他Linux发行版,虚拟机,真机均可)版本:18.04.4编译器:aarch64-linux-gnu-gcc(gcc版本5.5.0)调试器:主机:AARCH64-LINUX-GNU-GDB(GCC版本5.5.0)0.1VS代码版本:1.46.1Commit:CD9EA6488829F560DC949A8B2FB789F3CDCDC05F5DATE.0OS:Linuxx645.3.0-28-generic1.准备工作1.1.安装Ubuntu下载并安装Ubuntu,如本示例中所述:ubuntu18.041.2。安装VSCodeUbuntu版VSCode:下载地址1.3。安装VSCode插件并在扩展中添加C/C++(必备)C/C++Intellisense(可选)C/C++Snippets(可选)RemoteDevelopment(必备三件套,微软官方出品)Remote-WSLRemote-SSHRemote-Containers设备树插件EmbeddedLinuxDevKconfig(设备树插件依赖)1.4、下载Linux内核代码推荐下载:gitclonehttps://e.coding.net/benshush...-brlk_basic推荐理由:1、4.0版本很经典,适合学习2、代码仓库是本书的配套代码,比较完整。3、代码仓库已经配置了各种复杂的环境,比如:qemu网桥、根文件系统、qemu共享文件夹等。4、最后强调:前期的配套开发环境不需要重新发明轮子,纠结于小细节,先站在巨人的肩膀上,熟悉整个过程后,可以随时更换和修改这个环境1.5、搭建Linux内核编译环境Ubuntu18.04相关问题,可以直接百度搜索Linux环境:ubuntu18.04Linux安装依赖包:sudoapt-getinstallqemulibncurses5-devlibssl-devbuild-essentialopensslbisonbcflexgit的当然可以使用如下命令安装编译内核需要的所有依赖包sudoaptbuild-deplinux-image-genericLinux环境安装编译链:因为是Linux内核版本,因为使用的版本是4.0,所以5.xgcc跨链需要一次性安装ARM32/64跨链sudoaptinstallgcc-5-aarch64-linux-gnugcc-5-arm-linux-gnueabihf如果有其他版本的gcc跨链在系统中,您可以使用update-alternatives来管理它们。可以参考:update-alternatives命令主要参数如下update-alternatives--installlink:指向/etc/alternatives/name的符号引用:链接路径名称:该命令对应的可执行文件实际路径优先级:priority,自动模式下,数字越大优先级越高。2、Ubuntu下用VSCode搭建IDE到这里我们已经完成了Ubuntu、VScode及其插件的安装,接下来就可以使用VSCode编译调试2.1,使用VSCode打开内核源码2.2,开始编译kernel其实源码目录已经集成了编译、运行、调试所需的脚本,源码已经支持ARM32+debian或者ARM64+debian。本例以ARM64+Debian为例。在VSCode中按Ctrl+~调出系统终端,在终端中运行:./run_debian_arm64.shbuild_kernel编译完成:2.3。编译Rootfs编译ARM64版本的Debian系统rootfs$sudo./run_debian_arm64.shbuild_rootfs注意:这里需要root权限。编译后会生成一个rootfs_debian_arm64.ext4文件系统。2.4.运行Rootfs$./run_debian_arm64.shrun注意:运行此命令不需要Root权限。注:用户名:root密码:123运行成功后,如下图:登录成功后,如下图:2.5、测试Debian系统,因为是基于Debian系统,网络已经设置完成后,可以直接使用APT等命令安装在线包。下面是一个简单的测试:QEMU虚拟机可以通过VirtIO-NET技术生成虚拟网卡,通过NAT网桥技术与宿主机共享网络。首先使用ifconfig命令检查网络配置。可以看到生成了一个名为eth0的网卡设备,分配的IP地址为:10.0.2.15。使用aptupdate命令更新Debian系统的软件存储库。2.6.宿主机与QEMU虚拟机共享文件宿主机与QEMU虚拟机可以通过NET9P技术共享文件,这需要QEMU虚拟机的Linux内核启用NET9P内核模块。本平台已经支持宿主机与QEMU虚拟机共享文件,可以通过以下简单的方法进行测试。共享目录为:kmodules系统目录为:mnt效果如下图所示:在kmodules目录下创建一个test.c文件。我们以后会经常用到这个特性,比如把编译好的内核模块或者内核模块源码放到QEMU虚拟机中。一键式单步调试内核到这一步,基于WSL+VScode的环境已经搭建完成,这样你就可以得到一个集终端、文件管理器、git管理器、运行调试等为一体的集成IDE环境,这样就可以在纯WIN10下达到单步调试内核的目的,非常方便。3.1.配置内核调试命令。内核编译和调试命令都已经打包成脚本文件。有兴趣的童鞋可以去深入了解一下。这里以ARM64为例:./run_debian_arm64.shrundebug此时gdbserver已经在1234端口等待连接!3.2.ConfigureVSCodeDebug选项选择:Run(R)->AddConfiguration->C++(GDB/LDB)如下动图:添加如下配置信息:{//使用IntelliSense了解相关属性。//悬停以查看现有属性的描述。//如需更多信息,请访问:https://go.microsoft.com/fwlink/?linkid=830387"version":"0.2.0","configurations":[{"name":"(gdb)start",“type”:“cppdbg”,“request”:“launch”,“program”:“${workspaceFolder}/vmlinux”,“args”:[],“stopAtEntry”:true,“cwd”:“${workspaceFolder}","environment":[],"externalConsole":true,//调试时是否显示控制台窗口,一般设置为true显示控制台"MIMode":"gdb","miDebuggerPath":"/usr/local/bin/aarch64-linux-gnu-gdb","miDebuggerServerAddress":"localhost:1234","setupCommands":[{"description":"为gdb启用整洁打印","text":"-enable-漂亮的印刷”,"ignoreFailures":true}]}]}注意:localhost是本机IP,也就是ubuntu运行qemu的IP,哪个主机运行./run_debian_arm64.shrundebug取主机IP,因为是一样的此时host,所以填写localhostgdb路径:"miDebuggerPath":"/usr/local/bin/aarch64-linux-gnu-gdb"gdb监听端口:"miDebuggerServerAddress":"localhost",1234与上一节中的监听端口!3.3.一键调试【笨叔叔】在按下F5键之前,请先设置断点,比如在start_kernel函数中设置断点,直接打开init/main.c文件,在左侧点击鼠标左键start_kernel函数。设置断点经过3.1和3.2的配置,调试的前提条件已经实现。现在只需按F5键即可实现一键调试,如下图所示:3.4.更多GDB调试技巧在终端界面栏,切换到调试Console;输入命令,如:-execinforegisters,可以查看正在调试的寄存器:4.单步调试应用层+内核经过前面0~3的铺垫,我们已经具备了以下三个条件:1.内核完整(编译完成环境,调试环)+完整的Rootfs2.完整的qemu环境,包括:网络共享,桥接等,可以随时将host文件共享到qemu(本地kmodules文件夹《---》在虚拟机mnt文件夹内)3.完整的GDB调试环境可以实现内核的单步调试那么,对于以上情况,如果我们要调试或者参考一个Linux应用程序是如何访问内核的,是否可以做到呢?答案是肯定的!请看下面!4.1创建一个简单的APP程序在kmodules文件夹下新建一个test.c,内容如下:includeincludeincludeincludeincludeincludeincludeunsignedcharreadbuf[255];intcount=0;intmain(void){intfd;intretval;printf("helloworld!\n");fd=open("./README",O_RDONLY);if(fd==-1){perror("opendht11error\n");exit(-1);}printf("open./README\n");sleep(2);while(1){sleep(1);如果(计数++==0){printf(“计数=%d\n”,计数);}}close(fd);return0;}4.2编译调试输入编译命令:aarch64-inux-gnu-gcctest.c-otest在kmodules文件夹下得到一个新的测试应用。然后,按照第三节的环境进入单步调试内核。整体演示效果如下图所示。实现应用层到内核层的调用过程,只是一个简单的演示。更深层次的应用可以继续探索。5、单步调试modules+kernel5.1、简单测试代码准备在宿主机kmodules文件夹下创建一个简单的内核模块程序hello_drv.c和对应的Makefile文件内核模块程序hello_drv.c的内容示例:/*1includefiles2__initmodule_init()insmod3__exitmodule_exit()rmmod4GPLBSDAeplliGPLv2MIT5module_license(GPL)*/includeincludeincludeincludeincludeincludeincludestructclass*hello_class;structdevice*hello_dev;inthello_open(structinodeinode,structfileflips){printk("--------------%s------------\n",__FUNCTION__);return0;}staticssize_thello_write(structfilefile,constchar__userin,size_tsize,loff_t*off){printk("------------%s-----------\n",__FUNCTION__);无符号整数buf=88;copy_from_user(&buf,in,size);printk("writebufis:%d\n",buf);}staticssize_thello_read(structfilefile,char__userbuf,size_tnbytes,loff_t*ppos){printk("---------------%s------------\n",__FUNCTION__);??unsignedinta=100;copy_to_user(buf,&a,sizeof(int));}staticintmy_major=0;conststructfile_operationsmyfops={.open=hello_open,.write=hello_write,.read=hello_read,};staticint__inithello_init(void){printk("---------------%s------------\n",__FUNCTION__);??//appprintfmy_major=register_chrdev(0,"hello",&myfops);if(my_major<0){printk("regerror!\n");}elseprintk("my_major=%d",my_major);hello_class=class_create(THIS_MODULE,"hello_class");//创建hello_classhello_dev=device_create(hello_class,NULL,MKDEV(my_major,0),NULL,\"hello_dev");//creathello_dev--->>/dev/hello_devreturn0;}staticvoid__exithello_exit(void){printk("-------------%s------------\n",__FUNCTION__);??device_destroy(hello_class,MKDEV(my_major,0));class_destroy(hello_class);unregister_chrdev(my_major,"你好");}module_init(hello_init);module_exit(他llo_exit);MODULE_LICENSE("GPL");内核模块程序Makefile内容示例:ifeq($(KERNELRELEASE),)exportARCH=arm64exportCROSS_COMPILE=aarch64-linux-gnu-KERNELDIR=/home/jackeyt/runninglinuxkernel_4.0-rlk_basic#yourkerneldirctionNFS_DIR=$(KERNELDIR)/kmodulesCUR_DIR:=$(shellpwd)all:make-C$(KERNELDIR)M=$(CUR_DIR)modulesinstall:cp-ranf*.ko$(NFS_DIR)/clean:make-C$(KERNELDIR)M=$(CUR_DIR)clean.PHONY:modulesinstallcleanelseobj-m:=hello_drv.oendifcompilation注意这里的编译要在宿主机环境下,也就是在Ubuntu命令行下:make5.2,开始调试我们知道在使用insmod的时候,会调用对应的__init接口,而本例中hello_init是入口函数,所以简单测试一下,在内核中找到register_chrdev对应的接口定义,并设置断点,等待insmod执行完毕,观察运行过程内核并在内核中设置相应的断点:演示效果动画: