基于Openwrt + Qemu进行内核源码级调试
上篇文章—利用Qemu + Buildroot 进行内核源码级调试中介绍了通过Qemu + Buildroot 搭建Linux内核源码级调试,但是后面在使用,学习过程中感觉有点不方便的一点就是在Buildroot中添加自己的应用或者内核模块的时候,添加应用相对来说算比较简单方便的,但是添加内核模块的时候,由于Buildroot的根目录下面的make menuconfig选项中没有针对Kernel modules的选项, 相对来说Openwrt中增加自己的应用和内核模块相对比较方便,所以这里首先针对Openwrt + Qemu 搭建内核源码级调试环境,然后通过对Linux内核中Netlink通信机制的简单学习,实现一个简单应用程序和一个简单的内核模块,最后简单介绍往Openwrt中添加自己的应用程序和内核模块的方法。
一、Openwrt编译
1. 下载Openwrt源码
git clone https://github.com/openwrt/openwrt.git
2. 下载安装拓展包
$ ./scripts/feeds update -a
$ ./scripts/feeds install -a
3. 配置环境
我PC系统是Centos7,下面是预装的可能缺少的工具和库
sudo yum -y install gcc binutils patch bzip2 flex bison make autoconf gettext texinfo unzip sharutils libncurses5-dev ncurses-term zlib1g-dev wget perl-Thread-Queue openssl-devel zlib-static ncurses-devel gcc-c++
可能所列出的不全,可以在后面根据运行make menuconfig的时候出现的报错所显示的内容对应的再去一一的安装。
4. 编译
运行如下命令:
$ make menuconfig
将会出现如上图所示的对话框,进行编译选项配置,我直接选择的x86平台,其他先默认配置,后续根据自己的需求可以增加删除软件包,修改配置选项。然后保存配置直接编译。
$ make V=s
二、通过Qemu仿真Openwt
现在qemu对Openwrt的支持很好,通过如下命令既可以直接运行Openwrt生成的固件。
qemu-system-x86_64 -m 5120 -smp 4 -M pc -drive file=openwrt-x86-64-combined-ext4.img,if=none,id=openwrtdisk –device ich9-ahci,id=ahci -device ide-drive,drive=openwrtdisk,bus=ahci.0 -net nic,vlan=0,macaddr=52:54:00:AF:53:81,model=virtio -net user,vlan=0 -redir tcp:10023::23 -redir tcp:10080::80 –localtime
可能默认编译出来是没有这个文件openwrt-x86-64-combined-ext4.img
,要修改编译选项,取消“GZip images”选项,即不通过gzip打包,如下图所示。
上图串口打印还是一样熟悉的配方。为方便调试,可修改网络配置为自动分配。可直接修改Openwrt根目录下target/linux/x86/base-files/etc/config/network
文件按照如下方式修改即可。
可以通过在主机上Telnet到虚拟机进行操作,这样比较方便,当然也可以通过ssh,自己调试学习用,没考虑安全所以直接选择Telnet,因为在启动qemu-system-x86_64
的时候已经把虚拟机的23端口直接映射到主机10023端口,所以可以通过如下方式来访问:
同样通过访问主机的10080端口,可以访问虚拟机的80端口,如下图所示:
为方便调试,可以在主机上配置httpd服务,然后在虚拟机就可以通过wget来进行操作,如下图所示:
这样就不用每次编译都重启虚拟机。
三、Netlink通信机制
Netlink是linux提供的用于内核和用户态进程之间的通信方式。但是注意虽然Netlink主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的广播特性时。
一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,但是Netlink可以实现双工通信。
Netlink协议基于BSD socket和AF_NETLINK地址簇(address family),使用32位的端口号寻址(以前称作PID),每个Netlink协议(或称作总线,man手册中则称之为netlink family),通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。netlink具有以下特点:
① 支持全双工、异步通信(当然同步也支持)
② 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
③ 在内核空间使用专用的内核API接口
④ 支持多播(因此支持“总线”式通信,可实现消息订阅)
⑤ 在内核端可用于进程上下文与中断上下文
下面是一个简单的例子(例子摘自网上,自己修改了一点错误,主要在于理解Netlink通讯的机制)
客户端代码:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define MAX_PAYLOAD 1024 // maximum payload size
#define NETLINK_TEST 25 //自定义的协议
int main(int argc, char* argv[])
{
int state;
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL; //Netlink数据包头
struct iovec iov;
struct msghdr msg;
int sock_fd, retval;
int state_smg = 0;
// Create a socket
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
if(sock_fd == -1){
printf("error getting socket: %s", strerror(errno));
return -1;
}
// To prepare binding
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = 100; //A:设置源端端口号
src_addr.nl_groups =