Linux追踪技术 ftrace 原理

news2025/2/27 7:24:30

文章目录

  • 一、ftrace架构
  • 二、Ring Buffer
  • 三、tracer原理
    • 3.1 静态插桩
    • 3.2 动态插桩
  • 四、trace event
  • 五、kprobe event
  • 参考资料

一、ftrace架构

Linux ftrace中,trace类型最基础的就是:tracer和event这两类。如下图所示:

在这里插入图片描述
tracer发展出了function、function_graph、irqsoff、preemptoff、wakeup等一系列tracer。
event发展出tracepoint、kprobe、uprobe等一系列的event。

trace采集数据的方法一般分为两种:插桩、采样。

二、Ring Buffer

请参考:https://pwl999.blog.csdn.net/article/details/80349025

三、tracer原理

gcc使用了"-gp ,用_mcount()函数进行插桩,默认的_mcount函数是空操作:

#ifdef CONFIG_FUNCTION_TRACER

#ifdef CC_USING_FENTRY
# define function_hook	__fentry__
#else
# define function_hook	mcount
#endif

#ifdef CONFIG_DYNAMIC_FTRACE

ENTRY(function_hook)
	retq
END(function_hook)

每个函数入口插入对_mcount()函数的调用,就是gcc提供的插桩机制。我们可以重新定义_mcount()函数中的内容,调用想要执行的内容。

确切的mcount符号名称将取决于具体工具链。可能是:

“mcount”、“_mcount”,“__mcount“,“__fentry__”

通过运行以下命令来确定:

[root@localhost ~]# uname -r
3.10.0-957.el7.x86_64
[root@localhost ~]# echo 'main(){}' | gcc -x c -S -o - - -pg | grep mcount
        call    mcount

其中,“-pg “参数的作用是,给编译出来的函数开头都插入一条指令"call mcount”。通过编译的方式,我们可以给函数加上一个额外的hook点,但是这个额外"mcount"函数调用的开销是较大的,因此ftrace在内核启动的时候做了一件事,就是把内核每个函数里的第一条指令"call mcount”(5个字节),替换成了"nop"指令(五个字节),也就是一条空指令,表示什么都不做。

内核二进制文件vmlinux中附加了一个mcount_loc的段,这个段里记录了所有"call mcount"指令的地址。这样我们很容易就能找到每个函数的这个入口点。

在编译的时候调用recordmcount.pl脚本文件搜集所有mcount()函数的调用点,并且所有的调用点地址保存到section mcount_loc。

CONFIG_FTRACE_MCOUNT_RECORD=y
// /include/asm-generic/vmlinux.lds.h

#ifdef CONFIG_FTRACE_MCOUNT_RECORD
#define MCOUNT_REC()	. = ALIGN(8);				\
			VMLINUX_SYMBOL(__start_mcount_loc) = .; \
			*(__mcount_loc)				\
			VMLINUX_SYMBOL(__stop_mcount_loc) = .;

内核初始化时,遍历section mcount_loc的调用点地址,默认给所有“call mcount”替换成“nop”

start_kernel()
	-->ftrace_init()
extern unsigned long __start_mcount_loc[];
extern unsigned long __stop_mcount_loc[];

void __init ftrace_init(void)
{
	unsigned long count;
	......
	count = __stop_mcount_loc - __start_mcount_loc;

	ret = ftrace_dyn_table_alloc(count);
	if (ret)
		goto failed;

	last_ftrace_enabled = ftrace_enabled = 1;

	//“call mcount”替换成“nop”
	ret = ftrace_process_locs(NULL,
				  __start_mcount_loc,
				  __stop_mcount_loc);

	......
}

虽然是空指令,不过在内核的代码段里,这相当于给每个函数预留了5个字节。这样在需要的时候,内核可以再把这5个字节替换成callq指令,call的函数就可以指定成我们需要的函数了。

同时,内核的mcount_loc段里,虽然已经记录了每个函数"call mcount"的地址,不过对于ftrace来说,除了地址之外,它还需要一些额外的信息。
因此,在内核启动初始化的时候,ftrace又申请了新的内存来存放mcount_loc段中原来的地址信息,外加对每个地址的控制信息,最后释放了原来的mcount_loc段。

申请了新的内存来存放mcount_loc段:

ftrace_init()
	-->ftrace_process_locs()
		-->ftrace_allocate_pages()

将“call mcount”替换成“nop”:

ftrace_init()
	-->ftrace_process_locs()
		-->ftrace_update_code()
			 /*
			 * Do the initial record conversion from mcount jump
			 * to the NOP instructions.
			 */
			-->ftrace_code_disable()
				-->ftrace_make_nop()

ftrace_make_nop将call指令替换成nop指令:

int ftrace_make_nop(struct module *mod,
		    struct dyn_ftrace *rec, unsigned long addr)
{
	unsigned const char *new, *old;
	unsigned long ip = rec->ip;

	old = ftrace_call_replace(ip, addr);
	new = ftrace_nop_replace();

	/*
	 * On boot up, and when modules are loaded, the MCOUNT_ADDR
	 * is converted to a nop, and will never become MCOUNT_ADDR
	 * again. This code is either running before SMP (on boot up)
	 * or before the code will ever be executed (module load).
	 * We do not want to use the breakpoint version in this case,
	 * just modify the code directly.
	 */
	if (addr == MCOUNT_ADDR)
		return ftrace_modify_code_direct(rec->ip, old, new);

	/* Normal cases use add_brk_on_nop */
	WARN_ONCE(1, "invalid use of ftrace_make_nop");
	return -EINVAL;
}

相对应的函数将nop指令替换成call指令:

int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
	unsigned const char *new, *old;
	unsigned long ip = rec->ip;

	old = ftrace_nop_replace();
	new = ftrace_call_replace(ip, addr);

	/* Should only be called when module is loaded */
	return ftrace_modify_code_direct(rec->ip, old, new);
}

所以Linux内核在机器上启动之后,在内存中的代码段和数据结构就会发生变化。可以参考后面这张图,它描述了变化后的情况:
在这里插入图片描述
当我们需要用function tracer来trace某一个函数的时候,比如"echo do_mount > set_ftrace_filter"命令执行之后,do_mount()函数的第一条指令就会被替换成调用ftrace_caller的指令。如下所示:
在这里插入图片描述
这样,每调用一次do_mount()函数,它都会调用function_trace_call()函数,把ftrace function trace信息放入ring buffer里,再通过tracefs输出给用户。

图片来自于:极客时间容器实战高手课

详细原理请参考:
https://blog.csdn.net/pwl999/article/details/80627095
https://rtoax.blog.csdn.net/article/details/120925737

3.1 静态插桩

重定义_mcount()函数的方法来实现插桩。static ftrace一旦使能,对kernel中所有的函数(除开notrace、online、其他特殊函数)进行插桩,这带来的性能开销非常,一般不会使用。

3.2 动态插桩

调用者一般不需要对所有函数进行追踪,只会对感兴趣的一部分函数进行追踪。dynamic ftrace把不需要追踪的函数入口处指令“bl _mcount"替换成nop,这样基本上对性能无影响,对需要追踪的函数替换入口处"bl _mcount"为需要调用的函数。

如果设置了 CONFIG_DYNAMIC_FTRACE,则在禁用函数跟踪时系统将几乎没有开销运行。 它的工作方式是 mcount 函数调用(放置在每个内核函数的开头,由 gcc 中的 -pg 开关产生),开始指向一个简单的返回。 (启用 FTRACE 将在内核编译中包含 -pg 开关。)

在编译时,每个 C 文件对象都通过 recordmcount 程序(位于脚本目录中)运行。 该程序将解析 C 对象中的 ELF 标头以查找 .text 部分中调用 mcount 的所有位置。 从 gcc 版本 4.6 开始,为 x86 添加了 -mfentry,它调用“fentry”而不是“mcount”。 在创建堆栈帧之前调用它。

请注意,并非所有部分都被跟踪。 它们可能会被 notrace 阻止,或者以其他方式阻止,并且不会跟踪所有内联函数。 检查“available_filter_functions”文件以查看可以跟踪哪些功能。

创建了一个名为“__mcount_loc”的部分,其中包含对 .text 部分中所有 mcount/fentry 调用站点的引用。 recordmcount 程序将此部分重新链接回原始对象。 内核的最后链接阶段会将所有这些引用添加到一个表中。

在启动时,在初始化 SMP 之前,动态 ftrace 代码会扫描此表并将所有位置更新为 nop。 它还记录添加到 available_filter_functions 列表中的位置。 模块在加载时和执行之前进行处理。 当一个模块被卸载时,它也会从 ftrace 函数列表中删除它的函数。 这在模块卸载代码中是自动的,模块作者无需担心。

启用跟踪时,修改功能跟踪点的过程取决于体系结构。 旧的方法是使用 kstop_machine 来防止 CPU 与执行代码的竞争被修改(这可能导致 CPU 做不受欢迎的事情,特别是如果修改的代码跨越缓存(或页面)边界),并且 nop 被修补回调用 . 但是这一次,他们没有调用 mcount(这只是一个函数存根)。 他们现在调用 ftrace 基础设施。

修改函数跟踪点的新方法是在要修改的位置下一个断点,同步所有CPU,修改断点未覆盖的其余指令。 再次同步所有 CPU,然后将带有完成版本的断点移至 ftrace 调用站点。

有些archs甚至不需要进行同步,并且可以将新代码放在旧代码之上,而其他 CPU 同时执行它时不会出现任何问题。

记录被跟踪函数的一个特殊副作用是,我们现在可以有选择地选择希望跟踪哪些函数,以及希望mcount调用保留nops。

使用了两个文件,一个用于启用,一个用于禁用对指定功能的跟踪。 他们是:

set_ftrace_filter
set_ftrace_notrace

可以添加到这些文件中的可用函数列表如下:

available_filter_functions
[root@localhost tracing]# cat available_filter_functions | more
__startup_secondary_64
run_init_process
do_one_initcall
match_dev_by_uuid
name_to_dev_t
rootfs_mount
rootfs_mount
calibrate_delay
x86_pmu_extra_regs
x86_pmu_disable
collect_events
x86_pmu_event_idx
x86_pmu_sched_task
get_segment_base
perf_get_x86_pmu_capability
perf_assign_events
events_sysfs_show
x86_pmu_commit_txn
x86_pmu_add
x86_pmu_start_txn
set_attr_rdpmc
get_attr_rdpmc
x86_pmu_cancel_txn
x86_pmu_notifier
allocate_fake_cpuc
change_rdpmc
x86_perf_event_update
x86_pmu_stop
x86_pmu_del
x86_reserve_hardware
x86_pmu_event_init
x86_release_hardware
hw_perf_event_destroy
x86_add_exclusive
x86_del_exclusive
hw_perf_lbr_event_destroy
......

当我们需要用function tracer来trace某一个函数的时候,比如"echo do_mount > set_ftrace_filter"命令执行之后,do_mount()函数的第一条指令就会被替换成调用ftrace_caller的指令。

四、trace event

trace event的插桩使用的是tracepoint机制,该机制是一种静态的插桩方法,它需要静态的定义桩函数,并且在插桩位置显式调用。这种方法的好处是高效可靠,并且可以处于函数中的任何位置、方便的访问各种变量,坏处是不太灵活。对于kernel在重要的节点固定位置,插入了几百个trace event用于跟踪。

五、kprobe event

从前面几章看:trace event使用静态tracepoint插桩,function tracer使用“call mcount”的插桩点来动态插桩。既然都是插桩,为什么我们不使用功能强大的kprobe机制?

kprobe event就是这样的产物。krpobe event和trace event的功能一样,但是因为它采用的是kprobe插桩机制,所以它不需要预留插桩位置,可以动态的在任何位置进行插桩。开销会大一点,但是非常灵活,是一个非常方便的补充机制。

kprobe的主要原理是使用“断点异常”和“单步异常”两种异常指令来对任意地址进行插桩,在此基础之上实现了三种机制:

(1)kprobe: 可以被插入到内核的任何指令位置,在被插入指令之前调用kp.pre_handler(),在被插入指令之后调用kp.post_handler()
(2)jprobe: 只支持对函数进行插入
(3)kretprobe: 和jprobe类似,机制略有不同,会替换被探测函数的返回地址,让函数先执行插入的钩子函数,再恢复。

参考资料

Linux 3.10.0

极客时间容器实战高手课
https://blog.csdn.net/u012489236/article/details/119494200
https://blog.csdn.net/dog250/article/details/84667690
https://rtoax.blog.csdn.net/article/details/120925737
https://blog.csdn.net/jasonactions/article/details/120940323

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361457.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

界面控件DevExpress WPF Pivot Grid——拥有强大多维数据分析能力!

界面控件DevExpress WPF的Pivot Grid组件是一个类似excel的数据透视表,用于多维数据分析和跨选项卡报表生成。它拥有众多的布局自定义选项,允许开发者完全控制其UI且以用户为中心的功能使其易于部署。PS:DevExpress WPF拥有120个控件和库&…

Qt中的多线程

Qt中有多种方法实现多线程: QThreadQThreadPool和QPunnable(重用线程)Qt ConcurrentWorkerScript(QML中的线程)QThread 在上两篇文章中已经解释了,这里就不再赘述。 QThreadPoo和QRunnable(实现…

SpringBoot2核心技术-核心功能【05、Web开发】

目录 1、SpringMVC自动配置概览 2、简单功能分析 2.1、静态资源访问 1、静态资源目录 2、静态资源访问前缀 2.2、欢迎页支持 2.3、自定义 Favicon 2.4、静态资源配置原理 3、请求参数处理 0、请求映射 1、rest使用与原理 2、请求映射原理 1、普通参数与基本注解 …

PrivateLoader PPI服务发现RisePro恶意软件窃取分发信息

称为PrivateLoader的按安装付费(PPI)软件下载器服务正用于恶意软件RisePro的信息窃取。Flashpoint 于 2022 年 12月13日发现了新的窃取者,此前发现了在名为Russian Market的非法网络犯罪市场上使用该恶意软件泄露的“几组日志”。RisePro是一…

因“AI”而“深” 第四届OpenI/O 启智开发者大会高校开源专场25日开启!

中国算力网资源不断开发,开源社区治理及AI开源生态引来众多有才之士参与建设,国家级开放创新应用平台、NLP大模型等高新技术内容逐渐走向科研舞台上聚光灯的中心,新时代的大门缓缓打开。在启智社区,有一群人,他们年纪轻…

BEV感知:DETR3D

3D检测:DETR3D前言MethodImage Feature Extracting2D-to-3D Feature TransformationLoss实验结果前言 在这篇paper,作者提出了一个更优雅的2D与3D之间转换的算法在自动驾驶领域,它不依赖于深度信息的预测,这个框架被称之为DETR3D…

性能测试学习和性能瓶颈分析路线

很多企业招聘都只写性能测试,会使用LR,jmeter工具。其实会使用jmeter和LR进行性能测试还只是性能测试的第一步,离真正的性能测试工程师还很远,笔者也还在路上 .。 性能测试,都是要求测试系统性能,系统自然…

面试中经常被问到的【宏定义】,改变你对【C\C++】中宏定义的认识。

最近遇到挺多宏定义的代码,其实挺烦的,每次看复杂的宏定义看到一半就懵了,今天盘一盘它。本篇设计宏定义的原理、使用方法、使用技巧。 目录 一、宏定义原理 二、宏定义定义复杂功能函数 2.1 定义注册函数 三、宏定义实现条件编译 四、宏…

【OpenCV学习笔记01】- 初步使用OpenCV实现人脸识别

想要使用opencv实现人脸识别,我们需要做这样几步: 1.opencv-python的安装 这里我们使用的python的opencv-python库,在安装opencv-python库之前,我们需要安装numpy, matplotlib。 # 安装指令 # 安装 numpy pip install numpy # …

Chirp-Z变换(线性调频Z变换)原理

Chirp-Z变换(Chirp-Z Transform,CZT) 采用FFT算法可以很快地计算出全部DFT值,即Z变换在单位圆上的全部等间隔采样值。 在实际情况中,并不需要对整个单位圆的频谱进行分析,例如,对于窄带信号&am…

运动型蓝牙耳机推荐哪款、最新运动蓝牙耳机推荐

提起运动耳机,如今很多运动爱好者和职业教练员们,都会向萌新推荐骨传导运动耳机。骨传导耳机解决了入耳式蓝牙耳机掉落的问题,佩戴相当舒服。骨传导耳机在佩戴过程中解放了双耳,不会因为耳机堵住耳朵,听不到环境音&…

【Spring6】| Spring启示录、Spring概述

目录 一:Spring启示录 1. OCP开闭原则 2. 依赖倒置原则DIP 3. 控制反转IoC 二:Spring概述 1. Spring简介 2. Spring8大模块 3. Spring特点 一:Spring启示录 引言:前面我们已经学习了三层架构:表示层、业务层、…

【工作笔记】syslog,kern.log大量写入invalid cookie错误信息问题

任务描述 错误出现出现过四五次,应该是诊断单元tf卡读写出问题导致下面这条告警一直高频写入到/var/log/下的syslog、kern.log、messages中 Nov 23 06:25:12 embest kernel: omap_hsmmc 48060000.mmc: [omap_hsmmc_pre_dma_transfer] invalid cookie: data->hos…

将maven项目打包成可执行的jar(加入外部依赖)

在有些场景下我们需要将编写的Java工程编译打包成为一个完整的jar包,如果你的项目是使用maven构建的话可以通过以下方法来完成这个打包的过程。添加maven打包插件。在项目的pom.xml配置文件的build标签中添加以下代码,其中 mainClass 属性需要替换成你项…

多种调度模式下的光储电站经济性最优储能容量配置分析(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

安装MQTT Server遇到报错“cannot verify mosquitto.org‘s certificate”,该如何解决?

MQTT是基于发布/订阅的轻量级即时通讯协议,很适合用于低带宽、不稳定的网络中进行远程传感器和控制设备通讯等操作中。在我们的软件研发中,也经常使用MQTT协议进行消息通信等。今天来和大家分享一些关于在安装MQTT Server中遇到的疑难问题及解决思路。当…

为什么阳康后,感觉自己变傻了?

不少人在阳康后出现脑力下降的情况,好像脑子里被雾笼罩。脑雾并不是新名词,已经存在了十几年。以前慢性疲劳综合征患者和脑震荡患者会用它来形容自己的症状。脑雾其实是认知障碍,它可由多种原因引起。比如过度劳累、长期酗酒、缺乏睡眠、久坐…

Semi-supervised(半监督)布料缺陷检测实战

数据及源码链接见文末 1.任务目标和流程概述 对于常规的缺陷检测,常常需要我们准备好数据,使用分割或者检测的方法选择模型,进行训练。但是有一个问题。在日常生产中,我们接触到的往往都是正常的,缺陷数据往往很难收集,更何况我们还要打标签。我们能不能通过训练正常数据…

独立搭建 handle server

本节主要介绍,如何搭建一个与 GHR隔离的 handle sever,不与外界有任何连通。 下载文件 访问地址下载最新版:http://www.handle.net/download_hnr.html 这里以 9.3.0 版本作为讲解 解压服务端,解压客户端 # 解压 tar -xzvf handle-9.3.0-distribution.tar.gz# 到目录下 …

NestJS学习:图片上传、下载

参考 大神的这两篇文章讲的很详细,这里自己也来试一下 小满nestjs(第十三章 nestjs 上传图片-静态目录) 小满nestjs(第十四章 nestjs 下载图片) 上传图片 安装包 需要:multer 和 nestjs/platform-expre…