当前位置: 首页 > news >正文

Linux Ftrace Ops注册函数跟踪器与Hash过滤

Linux ftrace_ops 注册函数跟踪器与 hash 过滤

一、ftrace_ops 结构体详解

ftrace_ops 是 ftrace 跟踪器的核心数据结构,定义在 include/linux/ftrace.h:

struct ftrace_ops {
ftrace_func_t func; /* 跟踪回调函数 */
struct ftrace_ops *next; /* 链表中的下一个 ops */
unsigned long flags; /* 标志位 */
int nr_trampolines;
#ifdef CONFIG_DYNAMIC_FTRACE
unsigned long *notrace_hash; /* 不跟踪的哈希表 */
unsigned long *trace_hash; /* 要跟踪的哈希表 */
struct ftrace_ops_hash old_hash;
#endif
struct callback_head rcu; /* RCU 安全释放 */
struct module *private; /* 所属模块(模块跟踪用) */
};

关键字段:
- func:跟踪回调函数,每个被跟踪函数入口处调用。
- trace_hash:白名单哈希表,只有哈希中的函数才触发回调(空 = 全部)。
- notrace_hash:黑名单哈希表,哈希中的函数不触发回调。
- flags:控制行为,如 FTRACE_OPS_FL_RECURSION_SAFE 等。

二、register_ftrace_ops:核心注册路径

注册一个 ftrace_ops 的主函数定义在 kernel/trace/ftrace.c:

int register_ftrace_ops(struct ftrace_ops *ops)
{
struct ftrace_ops_hash old_hash_ops;
struct ftrace_hash *hash;
int ret;

/* 1. 如果未指定 hash,复制全局 hash */
if (!ops->trace_hash)
ops->trace_hash = alloc_ftrace_hash(FTRACE_HASH_BITS);

if (!ops->notrace_hash)
ops->notrace_hash = alloc_ftrace_hash(FTRACE_HASH_BITS);

/* 2. 设置默认的 trace_hash(如果为空) */
if (ftrace_hash_empty(ops->trace_hash)) {
/* 空 hash 意味着跟踪所有函数 */
}

/* 3. 调用 __register_ftrace_ops */
ret = __register_ftrace_ops(ops);
if (ret)
goto out;

/* 4. 更新动态 ftrace 记录 */
ret = ftrace_hash_move(ops, 0, ops->trace_hash);
if (ret) {
__unregister_ftrace_ops(ops);
goto out;
}

/* 5. 同步所有 CPU */
ftrace_sync();
return 0;
}

三、hash 过滤表的插入与删除

struct ftrace_hash 的定义:

struct ftrace_hash {
unsigned long size_bits; /* 哈希表大小的位数 */
struct hlist_head *hash_table; /* 哈希桶数组 */
unsigned int count; /* 条目数 */
unsigned int buckets; /* 桶数量 */
};

向 hash 中添加一个函数地址:

int ftrace_hash_add(struct ftrace_hash *hash, unsigned long ip)
{
struct ftrace_func_entry *entry;
unsigned long key;

key = hash_long(ip, hash->size_bits);

/* 检查是否已存在 */
hlist_for_each_entry(entry, &hash->hash_table[key], hlist) {
if (entry->ip == ip)
return 0; /* 已存在 */
}

/* 分配新条目 */
entry = kmalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;

entry->ip = ip;
hlist_add_head(&entry->hlist, &hash->hash_table[key]);
hash->count++;

return 0;
}

删除函数地址:

int ftrace_hash_remove(struct ftrace_hash *hash, unsigned long ip)
{
struct ftrace_func_entry *entry;
unsigned long key;

key = hash_long(ip, hash->size_bits);

hlist_for_each_entry(entry, &hash->hash_table[key], hlist) {
if (entry->ip == ip) {
hlist_del(&entry->hlist);
hash->count--;
kfree(entry);
return 0;
}
}
return -ENOENT;
}

四、动态 ftrace 的 mcount 调用与 hash 匹配

当内核编译了 CONFIG_DYNAMIC_FTRACE,每个函数入口的 mcount (或 __fentry__) 调用会跳转到 ftrace_caller,ftrace_caller 检查 ftrace_trace_function:

#ifdef CONFIG_DYNAMIC_FTRACE
asmlinkage void ftrace_caller(void)
{
/* 保存寄存器 */
SAVE_ARGS

/* 传递 parent_ip (返回地址) 和 ip (当前函数地址) */
movq %rdi, %rdx /* parent_ip -> rdx */
movq %rsi, %rdi /* ip -> rdi */

/* 调用全局 ftrace_trace_function */
call ftrace_trace_function

/* 恢复寄存器 */
RESTORE_ARGS
ret
}
#endif

ftrace_trace_function 在 __register_ftrace_ops 时被设置为 ops->func:

void ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct pt_regs *regs)
{
struct ftrace_ops *ops;

/* 遍历 ops 链表 */
do_for_each_ftrace_ops(ops, ftrace_ops_list) {
/* hash 匹配检查 */
if (ftrace_ops_test(ops, ip, rec)) {
/* 如果 ops 有自己的 trampoline */
if (ops->flags & FTRACE_OPS_FL_SAVE_REGS)
ops->func(ip, parent_ip, op, regs);
else
ops->func(ip, parent_ip, op, NULL);
}
} while_for_each_ftrace_ops(ops);
}

五、ftrace_ops_test:hash 过滤的核心检查

static int ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip,
void *regs)
{
struct ftrace_hash *trace_hash;
struct ftrace_hash *notrace_hash;
int ret;

/* RCU 安全读取 hash */
trace_hash = rcu_dereference_raw(ops->func_hash->trace_hash);
notrace_hash = rcu_dereference_raw(ops->func_hash->notrace_hash);

/* 检查黑名单优先 */
if (notrace_hash && !ftrace_hash_empty(notrace_hash)) {
if (ftrace_lookup_ip(notrace_hash, ip))
return 0; /* 在黑名单中,不跟踪 */
}

/* 白名单检查 */
if (trace_hash && !ftrace_hash_empty(trace_hash)) {
if (ftrace_lookup_ip(trace_hash, ip))
ret = 1; /* 在白名单中 */
else
ret = 0; /* 不在白名单中 */
} else {
ret = 1; /* 空白名单 = 跟踪所有 */
}

return ret;
}

ftrace_lookup_ip 的哈希查找:

int ftrace_lookup_ip(struct ftrace_hash *hash, unsigned long ip)
{
struct ftrace_func_entry *entry;
unsigned long key;

if (!hash)
return 0;

key = hash_long(ip, hash->size_bits);

hlist_for_each_entry_rcu(entry, &hash->hash_table[key], hlist) {
if (entry->ip == ip)
return 1;
}

return 0;
}

六、ftrace_ops 的 trampoline 优化

当只有一个 ftrace_ops 注册且 ops 有 FTRACE_OPS_FL_SAVE_REGS 标志时,ftrace 可以创建专用的蹦床(trampoline),避免遍历 ops 链表:

static void create_trampoline(struct ftrace_ops *ops)
{
unsigned long *tramp;

/* 在可执行内存中分配蹦床 */
tramp = (unsigned long *)alloc_percpu_page();

/* 写入机器码序列 */
/* 1. 保存寄存器 */
/* 2. 调用 ops->func */
/* 3. 恢复寄存器 */
/* 4. ret */

/* x86_64 示例 */
tramp[0] = 0x48; tramp[1] = 0x83; tramp[2] = 0xec; tramp[3] = 0x28; // sub rsp,40
tramp[4] = 0x48; tramp[5] = 0x89; tramp[6] = 0x3c; tramp[7] = 0x24; // mov [rsp],rdi
/* ... 保存更多寄存器 ... */
/* mov rax, ops->func */
tramp[12] = 0x48; tramp[13] = 0xb8;
*(void **)&tramp[14] = ops->func;
/* call rax */
tramp[22] = 0xff; tramp[23] = 0xd0;
/* 恢复寄存器 + ret */
tramp[24] = 0x48; tramp[25] = 0x83; tramp[26] = 0xc4; tramp[27] = 0x28;
tramp[28] = 0xc3;

ops->trampoline = tramp;
ops->flags |= FTRACE_OPS_FL_ALLOC_TRAMP;
}

之后 mcount 直接跳转到 ops->trampoline,不经过 ftrace_ops_list_func。

七、set_ftrace_filter 的用户态接口

用户态通过 tracefs 文件 set_ftrace_filter 和 set_ftrace_notrace 控制 hash:

static ssize_t
ftrace_filter_write(struct file *file, const char __user *ubuf,
size_t cnt, loff_t *ppos)
{
char *func_name;
int ret;

func_name = strndup_user(ubuf, cnt);
if (!func_name)
return -ENOMEM;

if (func_name[0] == '!') {
/* !func 表示从 hash 中删除 */
ret = ftrace_hash_remove(ops->trace_hash, func_name + 1);
} else if (strchr(func_name, '*')) {
/* 支持通配符匹配 */
ret = ftrace_match_add(ops->trace_hash, func_name);
} else {
/* 精确匹配添加 */
unsigned long ip = kallsyms_lookup_name(func_name);
if (!ip) {
ret = -ENOENT;
goto out;
}
ret = ftrace_hash_add(ops->trace_hash, ip);
}

if (ret == 0)
ret = ftrace_hash_move(ops, 0, ops->trace_hash);

out:
kfree(func_name);
return ret;
}

八、多个 ops 的优先级与排序

多个 ftrace_ops 注册时形成链表,按优先级排序:

- FTRACE_OPS_FL_GLOBAL: 使用全局蹦床,慢速路径。
- !FTRACE_OPS_FL_GLOBAL: 尽可能使用私有蹦床。
- FTRACE_OPS_FL_IPMODIFY: 可以修改 IP(用于 kprobe),最高优先级。

排序逻辑:

void ftrace_ops_init(struct ftrace_ops *ops)
{
struct ftrace_ops **p;

/* 按优先级插入 ops 链表 */
for (p = &ftrace_ops_list; *p; p = &(*p)->next) {
if ((*p)->flags & FTRACE_OPS_FL_IPMODIFY &&
!(ops->flags & FTRACE_OPS_FL_IPMODIFY))
continue; /* 带 IPMODIFY 的在前面 */

ops->next = *p;
*p = ops;
return;
}
*p = ops;
}

九、模块函数的跟踪

当模块加载时,ftrace 动态注册模块中的函数:

int ftrace_module_init(struct module *mod)
{
unsigned long *start, *end;

/* 查找模块的 mcount 调用表 */
start = mod->ftrace_callsites;
end = mod->ftrace_callsites + mod->num_ftrace_callsites;

/* 逐个添加 ftrace 记录 */
for (i = 0; start + i < end; i++) {
unsigned long ip = start[i];
struct dyn_ftrace *rec;

rec = ftrace_allocate_rec(ip, mod->name);
ftrace_match_rec(rec, ops); /* 检查过滤条件 */
}

/* 更新 mcount 跳转 */
ftrace_update_code();
return 0;
}

模块卸载时,所有相关 ftrace 记录被清理,对应的 nop 指令恢复。

十、hash 释放与 RCU 同步

hash 更新的 RCU 处理:

int ftrace_hash_move(struct ftrace_ops *ops, int enable,
struct ftrace_hash *hash)
{
struct ftrace_hash *old_hash;
int ret;

/* 备份旧 hash */
old_hash = ops->func_hash->trace_hash;

/* 设置新 hash */
rcu_assign_pointer(ops->func_hash->trace_hash, hash);

/* 更新所有被跟踪函数的 mcount 指令 */
ftrace_update_glob();

/* 等待 RCU 读者退出 */
synchronize_rcu();

/* 释放旧 hash */
free_ftrace_hash(old_hash);
return 0;
}

这使得 hash 更新时,正在执行的 ftrace_ops_test 仍然在旧 hash 上安全运行,更新完成后才释放旧内存。

http://www.jsqmd.com/news/1016849/

相关文章:

  • 核自旋量子比特在量子网络中的关键技术与应用
  • 从‘无法打印02’看联想M7206设计:小粉盒鼓粉分离机的常见故障点与日常维护避坑指南
  • 轻量级评论毒性识别:Flash+Detoxify落地实践
  • mbedTLS开发避坑指南:从PEM解析失败到SSL握手超时,这些错误码你遇到过吗?
  • 2026年PACE派驰轮胎抗老化性如何,性价比高品牌怎么收费 - 工业品网
  • MPC8309复位机制详解:从硬件信号到配置字与调试实战
  • Seaborn数据可视化核心原理与工程实践指南
  • 中卫市黄金回收门店推荐 五家靠谱店铺TOP排行榜及联系方式地址电话+白银回收+铂金回收+彩金回收当场结算 - 大熊猫898989
  • AutoHotkey脚本突然失效?可能是UAC权限的锅(附管理员权限自启解决方案)
  • 2026年总结苹果手机维修培训学校Top10,口碑好的学习机构如何选择 - 工业品网
  • Maven命令里加个单引号就能解决的事,为什么90%的人都会错?
  • 扩散模型在低光图像增强中的应用与SCEM模块解析
  • 数术工坊·八卷全书|本源创世版 完整体系总览
  • PyCharm镜像源配置错了?聊聊pip install背后的源优先级与冲突解决
  • 新手避坑指南:用Vivado ILA调试FPGA AD/DA数据采集,为什么你的波形显示不对?
  • 重庆市黄金回收门店推荐 五家靠谱店铺TOP排行榜及联系方式地址电话+白银回收+铂金回收+彩金回收当场结算 - 大熊猫898989
  • 避开这3个坑!用LabVIEW连接X-Plane 11进行UDP通信的实战避坑指南
  • 毛绒玩具厂主要分布在哪里?几大产区各有什么特点?
  • 你的STM32F103ZET6程序为啥下载失败?从FlyMcu报错信息到CH340驱动排查全指南
  • 2026年温州不锈钢带制造厂实力测评:304/316L/310S材质供应链深度分析 - 优质品牌商家
  • Elasticsearch 部署手册
  • OpenCV C++图像处理避坑指南:灰度变换的5个常见误区与高效写法
  • WebRTC VP8、VP9、H264如何选择:编码器策略与应用场景
  • 别再只盯着DO-178C了:聊聊机载软件工具鉴定的那些‘坑’与实战避雷指南
  • VS2022 切换定义(F12 / Go to Definition)反应慢
  • 多维聚合不是GROUP BY:数据立方体操作实战指南
  • Linux futex快速用户态互斥futex_wait与futex_wake
  • 从零开始:在 Windows 服务器上部署 Node.js 项目(小白实战教程)
  • TVA 视觉智能体二次开发实战(十二):双通信模式 Demo|C# 与 Python 互联互通 调用 TVA 视觉智能体自定义算子完整案例
  • 虚实同频,营区运维智控全域;全域孪生,营区态势一览无余