[funchook]内核问题定位分析和热补丁制作工具funchook使用方法

1. 缘起

该工具是起源于大神dog250的一篇帖子《Linux内核如何替换内核函数并调用原始函数》。通过替换原有函数,我们做很多事情。我常用的就是分析内核问题和制作内核热补丁。

虽然它很强大,帮助我解决了不少项目问题。但是每次使用都要重复定义一大堆函数指针,重复变量。繁杂而易错,让我不胜烦恼。

在使用中我一直在思考如何将通用的部分封装隐藏起来,提供简单易用的接口,使其更便捷、易用和高效。

2. 实现

突然有一天,灵感来了,很快就定义好相关的宏接口。细节部分推敲几次后也定了下来。就有了大家看到的funchook的初版代码(链接见文末)。

定义和实现部分是 funchook.c 和 funchook.h。module.c是其应用部分,上面有个简单的示例,和对应的注释,帮助大家快速上手,因为一共就HOOK_DEFINE、CALL_ORIG_FUNCION、HOOK_REGISTER、 HOOK_UNREGISTER 4个宏接口定义,非常简单,易于上手。

3. 示例

为了进一步帮助大家理解,我用一个实际例子,来帮助大家进一步掌握使用方法。

实现mt7621支持ethtool 设置rx-checksum 开关 为例。

ethtool工具在内核中是通过 dev_ioctl–>dev_ethtool–>通过ethcmd 来进行对应处理。
我想要确认 ethtool -K eth0 rx-checksum off 最终通过哪个ethcmd命令修改了网卡配置。

于是我们将要对 int dev_ethtool(struct net *net, struct ifreq *ifr) 函数进行hook替换,添加打印来获取到ethcmd的值。

3.1 使用HOOK_DEFINE定义 hook函数

HOOK_DEFINE的使用方法:
参数1:被替换的函数名,也就是dev_ethtool。
参数2:返回值类型,也就是int。(注意只写返回值类型,不需要携带static静态定义)
参数3~N:被替换函数的参数定义,也就是 struct net *net, struct ifreq *ifr。

简单来说就是将 函数名,返回值类型 插入到函数定义中参数第一,第二位置,再加上HOOK_DEFINE即可。

HOOK_DEFINE

HOOK_DEFINE(dev_ethtool, int, struct net *net, struct ifreq *ifr)
{
	//hook 函数的实现
}

3.2 hook函数实现

这是我们的核心环节,需要根据实际需求来实现hook函数。

对应需求不同有两种方式:

  1. hook函数中调用原有函数(大多分析问题时用)
    我们在分析内核问题时,经常用到这个,只需要在hook中增加相应debug信息,然后通过CALL_ORIG_FUNCION调用原有函数。不改变原有代码流程,相当于在原有函数前后增加了debug处理。

  2. hook函数中直接实现原有函数功能(大多修复函数bug时用)
    当我们发现原有函数存在bug,需要制作热补丁ko时,需要在hook中实现修复后的功能。然后直接hook替换原有函数工作,不需要在hook函数中调用原有函数。
    分析内核问题时,需要分析函数中间部分,这时也需要在hook中直接实现原有函数功能,再在合适位置添加debug信息,帮助我们分析定位问题。

3.2.1 hook函数中调用原有函数前处理

添加自己想要的处理,比如打印相关参数值,打印函数调用栈等等。
本次我们增加一个打印,将函数的ethcmd打印出来即可。

	struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
	void __user *useraddr = ifr->ifr_data;
	u32 ethcmd;
	int rc;

	if (!dev || !netif_device_present(dev))
		return -ENODEV;

	if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
		return -EFAULT;

    pr_info("before dev_ethtool: ethcmd=%u\n",  ethcmd);

3.2.2 hook函数中调用原有函数(hook直接替代原有函数则不需要这一步)

如果hook函数已经完全替代原有函数功能,则不需要调用原有函数。

CALL_ORIG_FUNCION使用方法:
参数1:被替换的函数名,也就是dev_ethtool。
参数2~N:被替换函数的变量列表,即 net, ifr。

简单来说就是将函数名插入到函数调用的第一位置,再加上 CALL_ORIG_FUNCION即可。

CALL_ORIG_FUNCION
因为dev_ethtool是有返回值的,所以我们用rc得到处理的返回值。

	rc = CALL_ORIG_FUNCION(dev_ethtool, net, ifr);

3.2.3 hook函数中调用原有函数后处理

添加自己想要的处理,比如打印返回值,打印相关参数值,打印函数调用栈等等。
本次我们不需要,留空即可。

因为dev_ethtool有返回值,我们通过return rc将返回值返回给上一层函数调用。

	return rc;

3.2.4 hook函数完整代码

dev_ethtool的hook替代函数完整实现如下:

HOOK_DEFINE(dev_ethtool, int, struct net *net, struct ifreq *ifr)
{
	struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
	void __user *useraddr = ifr->ifr_data;
	u32 ethcmd;
	int rc;

	if (!dev || !netif_device_present(dev))
		return -ENODEV;

	if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
		return -EFAULT;

    pr_info("before dev_ethtool: ethcmd=%u\n",  ethcmd);

	rc = CALL_ORIG_FUNCION(dev_ethtool, net, ifr);

	return rc;
}

3.3 hook函数的注册和注销

3.3.1 hook函数的注册

hook函数的注册,即使将hook函数替换原有函数开始在内核工作,后续内核中对原有函数的调用相当于调用hook函数。
我们将 hook的注册函数放到了 module init中,在模块加载时进行hook 注册。

HOOK_REGISTER只有一个参数,就是函数名,也就是 dev_ethtool。

HOOK_REGISTER(dev_ethtool);

3.3.1 hook函数的注销

hook函数的注销,即恢复原有函数,后续内核使用原有函数进行工作。
我们将 hook的注销函数放到了 module exit中,在模块卸载时进行hook 注销。

HOOK_UNREGISTER也只有一个参数,就是函数名,也就是 dev_ethtool。

HOOK_UNREGISTER(dev_ethtool);

3.4 module.c完整实现

module.c的完整实现如下,配合funchook.c和funckhook.h 即可编译出对应模块。
在实际应用中,你可以将hook定义和注册集成到你代码的任意地方。

#include <linux/module.h>
#include "funchook.h"

/* heades needed by patched function */
#include <net/sock.h>

/* module param defines */


/*
 * 1.use HOOK_DEFINE macro to define the hook function.
 *  1.1 if you need to call the original function in the hook function,
 *      you need to use CALL_ORIG_FUNCION macro.
 *  1.2 if you need to call other kernel exported function, just include the
 *      related header(s).
 *  1.3 if you need to call other kernel static function, you need to make a
 *      duplicate copy here.
 */
HOOK_DEFINE(dev_ethtool, int, struct net *net, struct ifreq *ifr)
{
	struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
	void __user *useraddr = ifr->ifr_data;
	u32 ethcmd;
	int rc;

	if (!dev || !netif_device_present(dev))
		return -ENODEV;

	if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
		return -EFAULT;

    pr_info("before dev_ethtool: ethcmd=%u\n",  ethcmd);

	rc = CALL_ORIG_FUNCION(dev_ethtool, net, ifr);

	return rc;
}

static __init int funchook_init(void)
{
	int ret = 0;

	/*
	 * 2. use HOOK_REGISTER macro to replace original function with hook function
	 */
	ret = HOOK_REGISTER(dev_ethtool);
	if (ret != 0)
	{
		pr_info("HOOK_REGISTER failed ret=%d\n", ret);
		return ret;
	}


	return ret;
}
module_init(funchook_init);

static __exit void funchook_exit(void)
{
	/*
	 * 3. use HOOK_UNREGISTER macro to restore original function.
	 */
	HOOK_UNREGISTER(dev_ethtool);
}
module_exit(funchook_exit);

MODULE_DESCRIPTION("hook sample");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.1");

3.5 调试

这个 warning是宏定义的stub实现产生的。stub函数只提供跳转作用,并不会实际执行,所以可以忽略这个问题。

[root@localhost funchook]# make
make -C /lib/modules/3.10.0-514.26.2.el7.x86_64/build M=/root/git/github/funchook1 modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'
  CC [M]  /root/git/github/funchook1/funchook.o
  CC [M]  /root/git/github/funchook1/module.o
In file included from /root/git/github/funchook1/module.c:2:0:
/root/git/github/funchook1/module.c: In function ‘stub_dev_ethtool’:
/root/git/github/funchook1/funchook.h:30:2: warning: ‘return’ with no value, in function returning non-void [-Wreturn-type]
  return; \
  ^
/root/git/github/funchook1/module.c:19:1: note: in expansion of macro ‘HOOK_DEFINE’
 HOOK_DEFINE(dev_ethtool, int, struct net *net, struct ifreq *ifr)
 ^
  LD [M]  /root/git/github/funchook1/sample.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /root/git/github/funchook1/sample.mod.o
  LD [M]  /root/git/github/funchook1/sample.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-514.26.2.el7.x86_64'
[root@localhost funchook1]# insmod sample.ko

执行 ethtool -K eth0 rx-checksum off命令,查看messages日志。

Dec 19 04:31:05 localhost kernel: [699670.756746] before dev_ethtool: ethcmd=55
Dec 19 04:31:05 localhost kernel: [699670.756781] before dev_ethtool: ethcmd=27
Dec 19 04:31:05 localhost kernel: [699670.756799] before dev_ethtool: ethcmd=20
Dec 19 04:31:05 localhost kernel: [699670.756801] before dev_ethtool: ethcmd=22
Dec 19 04:31:05 localhost kernel: [699670.756802] before dev_ethtool: ethcmd=24
Dec 19 04:31:05 localhost kernel: [699670.756803] before dev_ethtool: ethcmd=30
Dec 19 04:31:05 localhost kernel: [699670.756803] before dev_ethtool: ethcmd=33
Dec 19 04:31:05 localhost kernel: [699670.756804] before dev_ethtool: ethcmd=35
Dec 19 04:31:05 localhost kernel: [699670.756805] before dev_ethtool: ethcmd=43
Dec 19 04:31:05 localhost kernel: [699670.756805] before dev_ethtool: ethcmd=37
Dec 19 04:31:05 localhost kernel: [699670.756806] before dev_ethtool: ethcmd=58
Dec 19 04:31:05 localhost kernel: [699670.756807] before dev_ethtool: ethcmd=59  //ETHTOOL_SFEATURES
Dec 19 04:31:05 localhost kernel: [699670.756808] before dev_ethtool: ethcmd=20
Dec 19 04:31:05 localhost kernel: [699670.756809] before dev_ethtool: ethcmd=22
Dec 19 04:31:05 localhost kernel: [699670.756810] before dev_ethtool: ethcmd=24
Dec 19 04:31:05 localhost kernel: [699670.756810] before dev_ethtool: ethcmd=30
Dec 19 04:31:05 localhost kernel: [699670.756811] before dev_ethtool: ethcmd=33
Dec 19 04:31:05 localhost kernel: [699670.756812] before dev_ethtool: ethcmd=35
Dec 19 04:31:05 localhost kernel: [699670.756812] before dev_ethtool: ethcmd=43
Dec 19 04:31:05 localhost kernel: [699670.756813] before dev_ethtool: ethcmd=37
Dec 19 04:31:05 localhost kernel: [699670.756814] before dev_ethtool: ethcmd=58

通过查看ethcmd的相关定义,可知除了ethcmd=59,即ETHTOOL_SFEATURES是设置操作,其他都是get操作。从而得到 ethtool -K eth0 rx-checksum off实际执行的是ethcmd ETHTOOL_SFEATURES。

调试结束后,rmmod sample卸载调试模块即可。

4. github源码仓

github源码 funchook 欢迎下载使用,码字不易,大家多多点赞转发。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浮沉飘摇

码字不易,打赏随意。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值