linux ftrace(二) - 代码分析_ftrace.c 分析_Hacker_Albert的博客-程序员宅基地

技术标签: ftrace  

1.Ftrace实现原理

  • 静态探测点,是在内核代码中调用ftrace提供的相应接口实现,称之为静态,是因为在内核代码中写死的,静态编译到内核代码中的,在内核编译后,就不能再动态修改。在开启ftrace相关的内核配置选项后,内核中已经在一些关键的地方设置了静态探测点,需要使用时,即可查看到相应的信息。
  • 动态探测点,基本原理为:利用mcount机制,在内核编译时,在每个函数入口保留数个字节,然后在使用ftrace时,将保留的字节替换为需要的指令,比如跳转到需要的执行探测操作的代码。

1.1.Ftrace function tracer footstone: -pg

  当CONFIG_FUNCTION_TRACER打开时,编译时会增加-pg编译选项,gcc会在每个函数的入口处增加对mcount的调用。

  ftrace 支持动态trace,即可以跟踪内核和模块中任意的全局函数。它利用了gcc的-pg编译选项,在每个函数的开始增加一个stub,这样在需要的时候可以控制函数跳转到指定的代码中去执行。使用 -pg 选项会在编译得到的内核映像中加入大量的调试信息。一般情况下,只是在开发测试阶段激活 ftrace 支持,以调试内核,修复 bug 。最终用于发行版的内核则会关闭 -pg 选项,也就无法使用 ftrace。

  对于动态ftrace,有一个很重要的工作就是记录这些被-pg影响的函数,通过读文件/sys/kernel/debug/tracing/available_filter_functions来查看哪些函数是支持trace的。

    1 #include <stdio.h>
    2                                                                                                                                                                                                                                        
    3 int main()
    4 {
    
>>  5     int a = 0;
    6     return 0;
    7 }
  • gcc -m32 -S -pg main.c -o main_pg.s
  • gcc -m32 -S main.c -o main.s
    在这里插入图片描述
      当COFNIG_DYNAMIC_FTRACE被选中后, 内核在编译代码时,先指定-pg 选项编译生成.o文件,然后通过scripts/recordmcount.pl脚本来处理.o文件,经过scripts/recordmcount.pl处理之后,.o文件中新增了一个__mcount_loc段,在最终链接时被重定向,里面记录了所有插入了mcount的函数地址。

  当用户打开 ftrace 功能时,ftrace 将这些 nop 指令动态替换为 ftrace_caller,该函数将调用用户注册的 trace 函数。其具体的实现在相应 arch 的汇编代码中,以 x86 为例,在 entry_32.s 中:

  内核代码编译:

scripts/Makefile.build:
201 ifdef CONFIG_FTRACE_MCOUNT_RECORD
202 ifndef CC_USING_RECORD_MCOUNT
203 # compiler will not generate __mcount_loc use recordmcount or recordmcount.pl
204 ifdef BUILD_C_RECORDMCOUNT
205 ifeq ("$(origin RECORDMCOUNT_WARN)", "command line")
206   RECORDMCOUNT_FLAGS = -w
207 endif
208 # Due to recursion, we must skip empty.o.
209 # The empty.o file is created in the make process in order to determine
210 # the target endianness and word size. It is made before all other C
211 # files, including recordmcount.
212 sub_cmd_record_mcount =                                 \
213         if [ $(@) != "scripts/mod/empty.o" ]; then      \
214                 $(objtree)/scripts/recordmcount $(RECORDMCOUNT_FLAGS) "$(@)";   \
215         fi;
216 recordmcount_source := $(srctree)/scripts/recordmcount.c \
217                     $(srctree)/scripts/recordmcount.h
218 else
219 sub_cmd_record_mcount = set -e ; perl $(srctree)/scripts/recordmcount.pl "$(ARCH)" \                                                                                                                                                     
220         "$(if $(CONFIG_CPU_BIG_ENDIAN),big,little)" \
221         "$(if $(CONFIG_64BIT),64,32)" \
222         "$(OBJDUMP)" "$(OBJCOPY)" "$(CC) $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)" \
223         "$(LD) $(KBUILD_LDFLAGS)" "$(NM)" "$(RM)" "$(MV)" \
224         "$(if $(part-of-module),1,0)" "$(@)";
225 recordmcount_source := $(srctree)/scripts/recordmcount.pl
226 endif # BUILD_C_RECORDMCOUNT
227 cmd_record_mcount =                                             \
228         if [ "$(findstring $(CC_FLAGS_FTRACE),$(_c_flags))" =   \
229              "$(CC_FLAGS_FTRACE)" ]; then                       \
230                 $(sub_cmd_record_mcount)                        \
231         fi;
232 endif # CC_USING_RECORD_MCOUNT
233 endif # CONFIG_FTRACE_MCOUNT_RECORD

279 define rule_cc_o_c                                                              
280         $(call echo-cmd,checksrc) $(cmd_checksrc)                         \     
281         $(call cmd_and_fixdep,cc_o_c)                                     \
282         $(cmd_checkdoc)                                                   \
283         $(call echo-cmd,objtool) $(cmd_objtool)                           \
284         $(cmd_modversions_c)                                              \
285         $(call echo-cmd,record_mcount) $(cmd_record_mcount)                                                                                                                                                                              
286 endef

301 # Built-in and composite module parts                                           
302 $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
303         $(call cmd,force_checksrc)
304         $(call if_changed_rule,cc_o_c)

include/asm-generic/vmlinux.lds.h :

112 #ifdef CONFIG_FTRACE_MCOUNT_RECORD                                                                                                                                                                                                       
113 #define MCOUNT_REC()    . = ALIGN(8);                           \
114                         __start_mcount_loc = .;                 \
115                         KEEP(*(__mcount_loc))                   \
116                         __stop_mcount_loc = .;
117 #else
118 #define MCOUNT_REC()
119 #endif

  最终内核的链接脚本include/asm-generic/vmlinux.lds.h将__mcount_loc段的内容放在.init.data段中,并且通过__start_mcount_loc和__stop_mcount_loc两个全局符号来访问。

1.2.ftrace初始化

  gcc的-pg 选项在每个函数开始处增加了一条callq指令,它和对应的retq据统计会带来13%的性能开销,因此在内核的初始化阶段将这些callq指令全部修改为5 Byte的NOP指令: 66 66 66 66 90H,同时将这些指令的地址记录下来。

  scripts/recordmcount.pl过滤了kernel/trace/ftrace.o,没有为其增加__mcount_loc段,所以ftrace代码不会修改其自身的代码。ftrace_init在start_kernel中调用,早于kernel_init,此时不会有其它Core正在执行代码,因此也不用担心修改指令导致其它Core出现crash(系统运行时修改指令就要麻烦很多:被修改的指令正在其它Core上执行,5个字节的指令有可能跨两个cache line)。由于ftrace_init执行时间较早,所以.initcall中的初始化函数都是可以被trace的(在cmdline中增加"ftrace_filter="参数来指定要trace的函数)。

  6159 void __init ftrace_init(void)   
  6160 {
                           
  6161         extern unsigned long __start_mcount_loc[];
  6162         extern unsigned long __stop_mcount_loc[];
  6163         unsigned long count, flags;
  6164         int ret;
  6165         
  6166         local_irq_save(flags);
  6167         ret = ftrace_dyn_arch_init();
  6168         local_irq_restore(flags);
  6171         
  6172         count = __stop_mcount_loc - __start_mcount_loc;
  6180 
  6181         last_ftrace_enabled = ftrace_enabled = 1;
  6182                 
  6183         ret = ftrace_process_locs(NULL,
  6184                                   __start_mcount_loc,                                                                                                                                                                                 
  6185                                   __stop_mcount_loc);
  6186 
  6187         set_ftrace_early_filters();
  6188         
  6189         return;
  6190  failed:
  6191         ftrace_disabled = 1;
  6192 }   

  在ftrace_process_locs函数中,内核为__start_mcount_loc和__stop_mcount_loc之间的每个地址都创建一个struct dyn_ftrace结构,其中ip记录着函数开始的stub地址,ftrace_code_disable函数会将这个地址的内容替换为nop指令,这样在没有trace时,系统的性能几乎没有影响。

  当用户打开 ftrace 功能时,ftrace 将这些 nop 指令动态替换为 ftrace_caller,该函数将调用用户注册的 trace 函数。其具体的实现在相应 arch 的汇编代码中,以 x86 为例,在 entry_32.s 中:

  当开始trace时,内核根据函数名找到ip,将该地址处的nop指令修改为call指令,以控制其跳转到指定的位置。

  以 MIPS 为例:arch/mips/kernel/ftrace.c

  • 编译阶段
    scripts/recordmcount.{pl,c} 扫描所有 .text 中的 mcount 调用点并创建__mcount_loc 段
  • 引导阶段
    调用 ftrace_process_locs 把所有 mcount 调用点替换为 nop 指令:ftrace_make_nop()
  • 跟踪阶段
    调用 ftrace_run_update_code,替换回 mcount 调用点:ftrace_make_call()

代码:

  • arch/mips/kernel/mcount.S

refer to

  • https://www.jianshu.com/p/56a96de4e879
  • http://tinylab.org/ftrace-principle-and-practice/
  • https://blog.csdn.net/pwl999/article/details/80349025
  • https://blog.csdn.net/pwl999/article/details/80420905
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41028621/article/details/115520732

智能推荐

Python进程——multiprocessing.Event()|Barrier()-程序员宅基地

event实际上描述的是一种同步的处理事件,可以简单地理解为,不同的进程之间可以利用一些特殊的处理来等待其他进程处理完毕在event类同步处理时,多个进程将拥有用一个event实例,当调用wait()方法是将进入到阻塞状态,同时会设置阻塞标记为“False”,(待阻塞标记为“True"后才会接触阻塞状态),此时另外一个进程可以继续工作,并且通过set()方法将阻塞标记设置为“True”,这样之前...

文件路径的中文问题-程序员宅基地

需要打开中文文件名的链接,例如 http://www.abc.com/站点说明/文件1.txt 用英文名字文件可以打开: http://www.abc.com/zhandianshuoming/wenjian1.txt 但是用 http://www.abc.com/站点说明/文件1.txt 就无法访问,怎么解决? ---------------------------------

Edge浏览器被篡改主页_edge浏览器主页被篡改-程序员宅基地

两个有效方法解决edge浏览器主页篡改_edge浏览器主页被篡改

无刷无感电调原理-程序员宅基地

谨在《无感无刷直流电机之电调设计全攻略》文章基础上做补充电调开发难易排序:低压低速小负载有感电调 --- 高压高速大负载无感电调要注意的事项:内转子还是外转子电机、mos管选型、mos管限流电阻选取、mos管栅极驱动器选择、高低压分离、大电压回路、布局(寄生电感)、电流采样电阻功率、电流放大电路(差压取观测点)、ad直接采样的时间选取、硬件过零检测的滤波电容和分压电阻选择、电压比较器的供..._无感电调原理

我的研究生三年_研究生三年具体干嘛-程序员宅基地

前记: 我最初想将这些总结写到私密笔记本里,自己知道就好,不发表在博客,因为我怕,各种怕。没错,我一直都处在这些“怕”中。这或许是小时候生活在惧怕中的缘故。小时候特别怕严厉的父亲,幼小的心灵面对“阴森恐怖”的父亲(长大发现父亲还是很尊重我的想法),如果你还有抗争的想法,那就说明你的父亲还不够具有威慑力。这直接导致以下结果:1)主见不强。别人说去哪吃饭,我说随便;别人叫我点菜_研究生三年具体干嘛

随便推点

swiper动态渲染列表失效_swiper动态改变数据重新渲染页面_在线小白www的博客-程序员宅基地

尝试方法一:采用v-if,当列表请求成功后,再渲染swiper。结果依然不能正常渲染尝试方法二:采用nextTick,当页面加载完成后,再渲染swiper结果还是不能正常渲染尝试方法三:采用定时器在nextTick中加一个200的定时器,结果可以正常渲染了!!! onMounted(() => { nextTick(() => { setTimeout(()=>{ _swiper动态改变数据重新渲染页面

poj1679 判断最小生成树是不是唯一? 如果让我说:我只能说,实力决定一切。_举例说明最小生成树不唯一-程序员宅基地

思路:求次小生成树和最小生成树。#include#include#includeusing namespace std;const int inf=1<<30;const int maxn=111;int ans1,ans2;int dis[maxn];int map[maxn][maxn];int iToj[maxn][maxn];int vis[maxn];int u_举例说明最小生成树不唯一

Swiper动态加载不显示没效果解决方法_swiper动态加载数据没有加载全部-程序员宅基地

(1)使用Swiper中的属性var mySwiper = new Swiper('.swiper-container', { observer: true,//修改swiper自己或子元素时,自动初始化swiper observeParents: true,//修改swiper的父元素时,自动初始化swiper })(2)可能是因为页面图片太多导致图片未加载完,就把Swiper组件初始化了,所以导致图片显示不全或者..._swiper动态加载数据没有加载全部

华为Atlas200dk使用第四步------配置CANNtoolkit环境-程序员宅基地

本法重点是驱动与版本的选择!!!如果不知道驱动与版本请认真看教程选择正确的版本镜像进行烧录。创作快乐!!!_atlas200dk

java canonicalize_Java网络爬虫crawler4j学习笔记<8> URLCanonicalizer类-程序员宅基地

package edu.uci.ics.crawler4j.url;import java.net.MalformedURLException;import java.net.URI;import java.net.URISyntaxException;import java.net.URL;import java.net.URLDecoder;import java.net.URLEncoder..._canonicalize

BC渗透的常见切入点(总结)_bc漏洞-程序员宅基地

做了不少qp,BC渗透了,通宵了2个晚上干了几个盘子,简略的说下过程,做一下总结。首先说一下qp, 以我的渗透成功案例来说的话首先信息收集必不可少的,qp的特点是什么呢?他的后台会在服务器域名的后面以不同的端口形式架设 如图:关于端口可以发现,基础东西你们都懂。切入点:在app里面抓包,查找邮箱,充值的地方寻找sql注入或者意见反馈的位置XSS有一种情况是抓包显示127.0.0.1的抓不到包的情况,这种情况多于大盘子,它不一定走的是TCP UDP协议。可以参考T-ice表哥说的 P.._bc漏洞