ftrace

一、使用 #

https://www.kernel.org/doc/html/v4.19/trace/ftrace-uses.html

1. ftrace hook内核函数 #

1.1. 前置判断 #

  • 想要hook某个函数,需要内核开启编译选项进行支持
  • 正常默认编译选项中可以hook非内联的所有函数(包括静态函数)
  • 查看函数是否可以hook可以通过下面命令进行搜索
  • hook如果要调用自己函数(调用instruction_pointer_set),在arm64上只有5.5以上支持,x86上是3.x之后就支持了
1=> grep "tcp_options_write" /proc/kallsyms
2ffffffffafa03a90 t __pfx_tcp_options_write
3ffffffffafa03aa0 t tcp_options_write

1.2. 基础实现 #

  • Makefile
 1NAME := sdp_toa
 2
 3obj-m := $(NAME).o
 4
 5ifeq ($(KERNDIR), )
 6KDIR := /lib/modules/$(shell uname -r)/build
 7else
 8KDIR := $(KERNDIR)
 9endif
10PWD := $(shell pwd)
11COMPILE_DATE=$(shell date "+%Y%m%d")
12COMPILE_TIME=$(shell date -d $(COMPILE_DATE) "+%s")
13$(info COMPILE_TIME=$(COMPILE_TIME))
14
15$(NAME)-objs += main.o \
16	ftrace_hook.o
17
18# $(NAME)-y +=
19# $(NAME)-n +=
20
21ccflags-y += -DHIDS_FILTER_COMPILE_TIME=$(COMPILE_TIME)
22ifeq ($(DEBUG), 1)
23ccflags-y += -g -O0 -DDEBUG
24endif
25
26all:
27	$(MAKE) -C $(KDIR) M=$(PWD) modules
28
29clean :
30	$(MAKE) -C $(KDIR) M=$(PWD) modules clean
  • 源文件
 1// ftrace_hook_utils.h
 2#ifndef _FTRACE_HOOK_UTILS_H_
 3#define _FTRACE_HOOK_UTILS_H_
 4
 5#define __SC_DECL(t, a) t a
 6#define __FTRACE_HOOK_DEFINE_VAR0(m, ...)
 7#define __FTRACE_HOOK_DEFINE_VAR1(m, t, a, ...) m(t, a)
 8#define __FTRACE_HOOK_DEFINE_VAR2(m, t, a, ...) m(t, a), __FTRACE_HOOK_DEFINE_VAR1(m, __VA_ARGS__)
 9#define __FTRACE_HOOK_DEFINE_VAR3(m, t, a, ...) m(t, a), __FTRACE_HOOK_DEFINE_VAR2(m, __VA_ARGS__)
10#define __FTRACE_HOOK_DEFINE_VAR4(m, t, a, ...) m(t, a), __FTRACE_HOOK_DEFINE_VAR3(m, __VA_ARGS__)
11#define __FTRACE_HOOK_DEFINE_VAR5(m, t, a, ...) m(t, a), __FTRACE_HOOK_DEFINE_VAR4(m, __VA_ARGS__)
12#define __FTRACE_HOOK_DEFINE_VAR6(m, t, a, ...) m(t, a), __FTRACE_HOOK_DEFINE_VAR5(m, __VA_ARGS__)
13#define FTRACE_HOOK_DEFINE_VAR(n, ...) __FTRACE_HOOK_DEFINE_VAR##n(__VA_ARGS__)
14
15#endif  // _FTRACE_HOOK_UTILS_H_
  1// ftrace_hook.h
  2#ifndef _FTRACE_H_
  3#define _FTRACE_H_
  4
  5#include <linux/atomic.h>
  6#include <linux/ftrace.h>
  7
  8#include "ftrace_hook_utils.h"
  9
 10// 自定义log方式就定义hook_log,代码内部会使用hook_log_info等函数进行调用
 11#ifndef TAG
 12#define TAG "ftrace_hook"
 13#endif
 14
 15#ifndef HOOK_LOG_INFO
 16#ifdef DEBUG
 17#define HOOK_LOG_INFO(fmt, ...) pr_info("[%s] " fmt "\n", TAG, ##__VA_ARGS__)
 18#else
 19#define HOOK_LOG_INFO(fmt, ...) pr_info("[%s][I][%s:%d] " fmt "\n", TAG, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 20#endif
 21#endif
 22
 23#ifndef HOOK_LOG_WARN
 24#ifdef DEBUG
 25#define HOOK_LOG_WARN(fmt, ...) pr_warn("[%s] " fmt "\n", TAG, ##__VA_ARGS__)
 26#else
 27#define HOOK_LOG_WARN(fmt, ...) pr_info("[%s][W][%s:%d] " fmt "\n", TAG, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 28#endif
 29#endif
 30
 31#ifndef HOOK_LOG_ERR
 32#ifdef DEBUG
 33#define HOOK_LOG_ERR(fmt, ...) pr_err("[%s] " fmt "\n", TAG, ##__VA_ARGS__)
 34#else
 35#define HOOK_LOG_ERR(fmt, ...) pr_info("[%s][E][%s:%d] " fmt "\n", TAG, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 36#endif
 37#endif
 38
 39/**
 40 * @brief Hook项
 41 *
 42 */
 43struct ftrace_hook {
 44    /** 函数符号名 */
 45    const char *name;
 46
 47    /** 替换函数地址 */
 48    void *replacement;
 49
 50    /** 原始函数地址 */
 51    void **original;
 52
 53    /** 符号的原始地址 */
 54    unsigned long _address;
 55
 56    /** ftrace 相关结构体 */
 57    struct ftrace_ops ops;
 58};
 59
 60/**
 61 * @brief 正在运行的钩子数,如果大于 0 建议不要卸载,等待其变为 0
 62 */
 63extern atomic_t g_running_hooks;
 64
 65/**
 66 * @brief 钩子进入时调用
 67 *
 68 * @param name 函数符号名
 69 */
 70static inline void ftrace_hook_func_enter(const char *name) {
 71    // 增加引用计数,避免模块被释放
 72    atomic_inc(&g_running_hooks);
 73}
 74
 75/**
 76 * @brief 钩子结束时调用,由宏完成,不必手动调用
 77 *
 78 * @param name 函数符号名
 79 */
 80static inline void ftrace_hook_func_exit(const char *name) {
 81    // 减少引用计数
 82    atomic_dec(&g_running_hooks);
 83}
 84
 85/**
 86 * @brief hook多个函数
 87 *
 88 * @param hooks
 89 * @param count
 90 * @return int 0 表示全部成功,否则返回 -errno
 91 */
 92int ftrace_hook_multi_register(struct ftrace_hook hooks[], size_t count);
 93/**
 94 * @brief unhook多个函数,按照反方向unhook
 95 *
 96 * @param hooks
 97 * @param count
 98 */
 99void ftrace_hook_multi_unregister(struct ftrace_hook hooks[], size_t count);
100
101/**
102 * @brief 启用ftrace的hook功能
103 *
104 */
105void enable_ftrace_hook(void);
106/**
107 * @brief 禁用ftrace的hook功能
108 *
109 */
110void disable_ftrace_hook(void);
111/**
112 * @brief 返回当前是否启用ftrace的hook功能
113 *
114 * @return true
115 * @return false
116 */
117bool is_ftrace_hook_enabled(void);
118
119// 替换的函数名
120#define FTRACE_HOOK_REPLACE_NAME(name) __ftrace_hook_replace_##name
121// 原始函数名
122#define FTRACE_HOOK_ORIG_NAME(name) __ftrace_hook_orig_##name
123// 定义结构体
124#define FTRACE_HOOK_STRUCT(name)                                                            \
125    {                                                                                       \
126        #name, FTRACE_HOOK_REPLACE_NAME(name), (void **)&FTRACE_HOOK_ORIG_NAME(name), 0, {} \
127    }
128
129/**
130 * @brief 定义要替换的函数
131 * @param n 函数参数个数
132 * @param name 函数名
133 * @param return_type 函数返回类型
134 * @param ... 函数参数按照type, var的方式传入
135 *
136 */
137#define FTRACE_HOOK_FUNC_REPLACE(n, name, return_type, ...)                                                \
138    static return_type (*FTRACE_HOOK_ORIG_NAME(name))(FTRACE_HOOK_DEFINE_VAR(n, __SC_DECL, __VA_ARGS__));  \
139    static return_type FTRACE_HOOK_REPLACE_NAME(name)(FTRACE_HOOK_DEFINE_VAR(n, __SC_DECL, __VA_ARGS__)) { \
140        static const char *__name = #name;                                                                        \
141        ftrace_hook_func_enter(#name);
142
143// 结束符
144#define FTRACE_HOOK_FUNC_END }
145
146// 调用原始函数
147#define FTRACE_HOOK_FUNC_ORIG(name, ...) FTRACE_HOOK_ORIG_NAME(name)(__VA_ARGS__)
148
149/**
150 * @brief 钩子返回,减少引用计数
151 * @note 所有钩子中必须使用这个宏返回数据
152 *
153 */
154#define FTRACE_HOOK_RETURN(x)          \
155    do {                               \
156        ftrace_hook_func_exit(__name); \
157        return x;                      \
158    } while (0)
159
160#define FTRACE_HOOK_RETURN_VOID()      \
161    do {                               \
162        ftrace_hook_func_exit(__name); \
163        return;                        \
164    } while (0)
165
166#endif  // _FTRACE_H_
  1// ftrace_hook.c
  2#include "ftrace_hook.h"
  3
  4// 记录当前是否启用hook功能,0表示未启用,1表示启用
  5atomic_t g_hook_enable = ATOMIC_INIT(0);
  6
  7void enable_ftrace_hook(void) { atomic_set_release(&g_hook_enable, 1); }
  8
  9void disable_ftrace_hook(void) { atomic_set_release(&g_hook_enable, 0); }
 10
 11bool is_ftrace_hook_enabled(void) { return atomic_read_acquire(&g_hook_enable) == 1; }
 12
 13atomic_t g_running_hooks = ATOMIC_INIT(0);
 14
 15/**
 16 * @brief 中转函数,在每次调用hook函数前调用,如果regs->ip被修改,则调用修改后的函数,否则调用原始函数
 17 *
 18 */
 19static void notrace ftrace_hook_trampoline(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops,
 20                                           struct pt_regs *regs) {
 21    struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
 22    ftrace_hook_func_enter("ftrace_hook_trampoline");
 23
 24    if (hook->replacement) {
 25        // 修改调用自己的函数
 26        instruction_pointer_set(regs, (unsigned long)hook->replacement);
 27    }
 28
 29    ftrace_hook_func_exit("ftrace_hook_trampoline");
 30}
 31
 32static int hook_by_ftrace(struct ftrace_hook *hook) {
 33    int ret;
 34
 35    // 查找符号
 36    hook->_address = kallsyms_lookup_name(hook->name);
 37    if (!hook->_address) {
 38        HOOK_LOG_WARN("Failed to resolve %s address", hook->name);
 39        return -ENOENT;
 40    }
 41
 42    // 跳过开头的 ftrace entry 指令,避免再次进入
 43    if (hook->original) {
 44        *(hook->original) = (void *)(hook->_address + MCOUNT_INSN_SIZE);
 45    }
 46
 47    hook->ops.func = ftrace_hook_trampoline;
 48    // FTRACE_OPS_FL_SAVE_REGS: 需要访问参数
 49    // FTRACE_OPS_FL_RECURSION_SAFE: 我们自己处理重入,减少性能损失
 50    // FTRACE_OPS_FL_IPMODIFY: 我们需要修改 IP 寄存器
 51    hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION_SAFE | FTRACE_OPS_FL_IPMODIFY;
 52
 53    // 此函数支持符号名唯一的hook,如果存在多个函数同一个符号则需要使用ftrace_set_filter_ip来进行处理
 54    ret = ftrace_set_filter(&hook->ops, (unsigned char *)hook->name, strlen(hook->name), 0);
 55    if (ret < 0) {
 56        HOOK_LOG_WARN("Failed to set ftrace ip filter(%s, 0x%p): %d", hook->name, (void *)hook->_address, ret);
 57        return ret;
 58    }
 59
 60    ret = register_ftrace_function(&hook->ops);
 61    if (ret < 0) {
 62        // 清理
 63        ftrace_set_notrace(&hook->ops, (unsigned char *)hook->name, strlen(hook->name), 0);
 64        HOOK_LOG_WARN("Failed to register ftrace function(%s): %d", hook->name, ret);
 65        return ret;
 66    }
 67
 68    return 0;
 69}
 70
 71static void unhook_by_ftrace(struct ftrace_hook *hook) {
 72    int ret;
 73    ret = unregister_ftrace_function(&hook->ops);
 74    if (ret < 0) {
 75        HOOK_LOG_WARN("Failed to unregister ftrace function(%s): %d", hook->name, ret);
 76    }
 77
 78    ret = ftrace_set_notrace(&hook->ops, (unsigned char *)hook->name, strlen(hook->name), 0);
 79    if (ret < 0) {
 80        HOOK_LOG_WARN("Failed to remove ftrace ip filter(%s, 0x%p): %d", hook->name, (void *)hook->_address, ret);
 81    }
 82}
 83
 84static int ftrace_hook_register(struct ftrace_hook *hook) {
 85    if (!hook || !hook->name || !hook->name[0] || !hook->replacement) {
 86        return -EINVAL;
 87    }
 88
 89    return hook_by_ftrace(hook);
 90}
 91
 92static void ftrace_hook_unregister(struct ftrace_hook *hook) {
 93    if (!hook || !hook->name || !hook->name[0]) {
 94        return;
 95    }
 96
 97    unhook_by_ftrace(hook);
 98}
 99
100/**
101 * @brief 注册多个
102 *
103 * @param hooks
104 * @param count
105 * @return int 0 表示全部成功,否则返回 -errno
106 */
107int ftrace_hook_multi_register(struct ftrace_hook hooks[], size_t count) {
108    int ret = 0;
109    int i = 0;
110
111    if (unlikely(hooks == NULL)) {
112        return -EINVAL;
113    }
114
115    for (; i < count; ++i) {
116        ret = ftrace_hook_register(hooks + i);
117        if (ret < 0) {
118            HOOK_LOG_WARN("Failed to register hook(%s): %d", hooks[i].name, ret);
119            goto failed;
120        }
121    }
122
123    return 0;
124
125failed:
126    for (--i; i >= 0; --i) {
127        ftrace_hook_unregister(hooks + i);
128    }
129    return ret;
130}
131
132/**
133 * @brief 注销多个
134 *
135 * @param hooks
136 * @param count
137 */
138void ftrace_hook_multi_unregister(struct ftrace_hook hooks[], size_t count) {
139    int i = (int)count;
140    if (unlikely(hooks == NULL)) {
141        return;
142    }
143    for (--i; i >= 0; --i) {
144        ftrace_hook_unregister(hooks + i);
145    }
146}

1.3. 使用示例 #

 1// main.c
 2#include "ftrace_hook.h"
 3
 4// hook tcp_established_options
 5// 下面的宏定义了需要赋值的原函数指针并声明了替换的函数,在函数开头会添加引用计数
 6FTRACE_HOOK_FUNC_REPLACE(4, tcp_established_options, unsigned int, struct sock *, sk, struct sk_buff *, skb,
 7                         struct tcp_out_options *, opts, struct tcp_md5sig_key **, md5) {
 8    unsigned int size = 0;
 9    // 调用原函数
10    size = FTRACE_HOOK_FUNC_ORIG(tcp_established_options, sk, skb, opts, md5);
11    // do something
12    FTRACE_HOOK_RETURN(size);   // 使用宏进行返回,会将引用计数减一
13}
14FTRACE_HOOK_FUNC_END
15
16static struct ftrace_hook toa_hooks[] = {
17    FTRACE_HOOK_STRUCT(tcp_established_options),
18};
19
20int toa_init(void) {
21    int ret;
22    ret = ftrace_hook_multi_register(toa_hooks, ARRAY_SIZE(toa_hooks));
23
24    if (ret) {
25        // do something
26        goto hook_err;
27    }
28
29    // 上面只是注册,需要下面的调用来启动hook函数调用
30    enable_ftrace_hook();
31    return 0;
32hook_err:
33    return ret;
34}
35
36void toa_exit(void) {
37    int running_hooks;
38
39    disable_ftrace_hook();  // 非必要,不调也可以卸载
40    ftrace_hook_multi_unregister(toa_hooks, ARRAY_SIZE(toa_hooks));
41
42    // 下面的处理防止驱动退出后,已经调用到hook函数内部的函数返回到非法内存地址
43    // 这里等待引用计数减到0后再退出释放内存
44    while ((running_hooks = atomic_read_acquire(&g_running_hooks))) {
45        SDP_LOG_INFO("waiting for running hooks %d", running_hooks);
46        msleep(1);
47    }
48}
49
50MODULE_LICENSE("GPL");
51MODULE_DESCRIPTION("ftrace test");
52MODULE_AUTHOR("xxx");
53module_init(toa_init);
54module_exit(toa_exit);