1. 程式人生 > 其它 >[funchook]核心問題定位分析和熱補丁製作工具funchook使用方法

[funchook]核心問題定位分析和熱補丁製作工具funchook使用方法

技術標籤:funchookfunchooklinuxgithub

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函式只提供跳轉作用,並不會實際執行,所以可以忽略這個問題。

[[email protected] 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'
[[email protected] 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 歡迎下載使用,碼字不易,大家多多點贊轉發。