Linux内核6.6/6.8版本Ftrace钩子无限循环问题求助
内核6.6/6.8下Ftrace回调无限循环问题解决
问题场景
内核版本6.8,使用Ftrace钩子时,通过within_module(parent_ip, THIS_MODULE)判断是否来自当前模块以避免递归,但该检查在6.6、6.8版本中始终返回false,导致Ftrace触发无限循环;相同代码在内核5.15版本可正常运行,怀疑是内核模块布局相关提交导致地址检查失效。
回调代码如下:
static void notrace ftrace_callback_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct ftrace_regs *fregs) { struct pt_regs *regs = ftrace_get_regs(fregs); struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); #if USE_FENTRY_OFFSET regs->ip = (unsigned long)hook->function; #else if (!within_module(parent_ip, THIS_MODULE)){ LOG_DEBUG("not true within_module(parent_ip, THIS_MODULE)"); regs->ip = (unsigned long)hook->function; } else { LOG_DEBUG("true within_module(parent_ip, THIS_MODULE)"); } #endif //USE_FENTRY_OFFSET }
原因分析
内核6.6及以后版本对模块内存布局做了调整,将代码段拆分为多个独立段(如.text、.fentry、.exit.text等),而within_module函数默认仅检查模块核心.text段的地址范围。Ftrace回调函数通常被放置在.fentry或其他非.text段,导致parent_ip(回调自身的地址)不在within_module的检查范围内,因此始终返回false,无法阻止递归触发。
在5.15及更早版本中,模块所有代码都集中在.text段,within_module可以正确识别回调函数的地址归属。
解决方案
方案1:直接检查地址是否为回调函数自身(最可靠)
递归的本质是回调函数触发自身的Ftrace钩子,直接对比ip或parent_ip与回调函数的地址,精准阻止递归:
static void notrace ftrace_callback_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct ftrace_regs *fregs) { struct pt_regs *regs = ftrace_get_regs(fregs); struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); // 检查当前执行IP或父IP是否为回调函数本身,避免递归 if (ip != (unsigned long)ftrace_callback_handler && parent_ip != (unsigned long)ftrace_callback_handler) { regs->ip = (unsigned long)hook->function; } }
方案2:扩展模块地址检查范围
手动遍历模块的所有代码段,判断地址是否属于当前模块的任意段:
#include <linux/module.h> #include <linux/elf.h> static bool within_any_module_segment(unsigned long addr, struct module *mod) { struct module_sect_attrs *sect_attrs = mod->sect_attrs; struct module_section *sec; if (!sect_attrs) return false; // 遍历模块所有段,检查地址是否在段范围内 list_for_each_entry(sec, §_attrs->sections, list) { if (addr >= sec->addr && addr < sec->addr + sec->size) return true; } return false; } static void notrace ftrace_callback_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct ftrace_regs *fregs) { struct pt_regs *regs = ftrace_get_regs(fregs); struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); if (!within_any_module_segment(parent_ip, THIS_MODULE)) { regs->ip = (unsigned long)hook->function; } }
方案3:启用USE_FENTRY_OFFSET(推荐)
如果内核配置支持,启用USE_FENTRY_OFFSET,该机制通过偏移量跳转,本身不会触发回调函数的递归调用:
#define USE_FENTRY_OFFSET 1 static void notrace ftrace_callback_handler(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct ftrace_regs *fregs) { struct pt_regs *regs = ftrace_get_regs(fregs); struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops); #if USE_FENTRY_OFFSET regs->ip = (unsigned long)hook->function; #endif //USE_FENTRY_OFFSET }
内容的提问来源于stack exchange,提问作者hongyun
相关产品推荐
相关产品推荐

