Linux-ftrace-双nop机制的实现

Linux 内核调试工具ftrace 之(NOP动态插桩的实现原理)

ftrace 是 Linux 内核中的一种跟踪工具,主要用于性能分析、调试和内核代码的执行跟踪。它通过在内核代码的关键点插入探针(probe)来记录函数调用和执行信息。这对于开发者排查问题、优化性能或者理解内核行为非常有用。

linux中主要支持两种ftrace的实现方式:

  1. _mcount机制,(主要在内核为5.10前版本),可见文章《ftrace之_mcount的实现原理》
  2. NOP指令动态插桩机制(主要在内核为5.10及以后版本)

下面将深入介绍双NOP指令动态插桩机制的实现原理:

NOP指令动态插桩机制的实现

复制代码
 * Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before
 * the regular function prologue. For an enabled callsite, ftrace_init_nop() and
 * ftrace_make_call() have patched those NOPs to:
 *
 * 	MOV	X9, LR
 * 	BL	<entry>
 *
 * ... where <entry> is either ftrace_caller or ftrace_regs_caller.
  • gcc编译内核时加上 -fpatchable-function-entry=2 选项将会在每个支持被插桩的函数最前面插入两条NOP指令。
  • nop本身就是动态插桩机制,当需要追踪该函数时,才会将该函数前面的nop指令替换为MOV X9, LRBL <entry><entry>ftrace_callerftrace_regs_caller)。

NOP入口的分析

1. 下面是实际的编译的驱动函数汇编代码:
复制代码
0000000000000000 <gps_pcie_tty_close>:
   0:   d503201f        nop
   4:   d503201f        nop
   8:   d503233f        paciasp
   c:   a9bf7bfd        stp     x29, x30, [sp, #-16]!
  10:   aa0103e2        mov     x2, x1
  14:   910003fd        mov     x29, sp
  18:   aa0003e1        mov     x1, x0
  1c:   f941d000        ldr     x0, [x0, #928]
  20:   94000000        bl      0 <tty_port_close>
  24:   a8c17bfd        ldp     x29, x30, [sp], #16
  28:   d50323bf        autiasp
  2c:   d65f03c0        ret
2. 当该函数需要被追踪,则将nop换成MOV X9, LRBL ftrace_caller(这里以ftrace_caller为例)。
复制代码
 * Each instrumented function follows the AAPCS, so here x0-x8 and x18-x30 are
live (x18 holds the Shadow Call Stack pointer), and x9-x17 are safe to clobber.
  • 每个支持被追踪的函数应该遵守AAPCS规定。根据 AAPCS 的规定,寄存器可分为调用者保存和被调用者保存两类。调用者保存的寄存器需要在调用函数前由调用者保存其值,而被调用者保存的寄存器则由被调用的函数负责保存和恢复。寄存器 x0x8 以及 x18x30 被视为活跃寄存器(其中 x18 保存影子调用栈指针),而寄存器 x9x17 则可安全地被覆盖。
  • 因此x9寄存器是可以直接用的,所以可以用来存调用者的返回地址即将LR_A存入到x9(可以发现这里和_mcount的处理方式不同,不用再先保存寄存器了)。
  • 接下来就进入ftrace_callerftrace_regs_caller
    此时栈分配如下图:
3. ftrace_callerftrace_regs_caller中的任务
复制代码
YM_CODE_START(ftrace_regs_caller)
#ifdef BTI_C
	BTI_C
#endif
	ftrace_regs_entry	1
	b	ftrace_common
SYM_CODE_END(ftrace_regs_caller)

SYM_CODE_START(ftrace_caller)
#ifdef BTI_C
	BTI_C
#endif
	ftrace_regs_entry	0
	b	ftrace_common
SYM_CODE_END(ftrace_caller)

ftrace_callerftrace_regs_caller都会跳转至ftrace_regs_entry,然后全部跳转至b ftrace_common(b跳转指令不会将返回地址存到lr寄存器中)。接下来分析一下ftrace_regs_entryftrace_common

4. ftrace_regs_entry
复制代码
    .macro  ftrace_regs_entry, allregs=0
	/* Make room for pt_regs, plus a callee frame */
	sub	sp, sp, #(S_FRAME_SIZE + 16)

	/* Save function arguments (and x9 for simplicity) */
	stp	x0, x1, [sp, #S_X0]
	stp	x2, x3, [sp, #S_X2]
	stp	x4, x5, [sp, #S_X4]
	stp	x6, x7, [sp, #S_X6]
	stp	x8, x9, [sp, #S_X8]

	/* Optionally save the callee-saved registers, always save the FP */
	.if \allregs == 1
	//这里是allregs == 1时额外要保存的现场
	stp	x10, x11, [sp, #S_X10]
	stp	x12, x13, [sp, #S_X12]
	stp	x14, x15, [sp, #S_X14]
	stp	x16, x17, [sp, #S_X16]
	stp	x18, x19, [sp, #S_X18]
	stp	x20, x21, [sp, #S_X20]
	stp	x22, x23, [sp, #S_X22]
	stp	x24, x25, [sp, #S_X24]
	stp	x26, x27, [sp, #S_X26]
	stp	x28, x29, [sp, #S_X28]
	.else
	//这里是allregs == 0时额外要保存的现场
	str	x29, [sp, #S_FP]
	.endif

	/* Save the callsite's SP and LR */
	add	x10, sp, #(S_FRAME_SIZE + 16)
	stp	x9, x10, [sp, #S_LR]

	/* Save the PC after the ftrace callsite */
	str	x30, [sp, #S_PC]

	/* Create a frame record for the callsite above pt_regs */
	stp	x29, x9, [sp, #S_FRAME_SIZE]
	add	x29, sp, #S_FRAME_SIZE

	/* Create our frame record within pt_regs. */
	stp	x29, x30, [sp, #S_STACKFRAME]
	add	x29, sp, #S_STACKFRAME
	.endm
  • 在上面的现场保存后函数栈的分布如下图:
5. 跳转到ftrace_common
复制代码
SYM_CODE_START(ftrace_common)
	sub	x0, x30, #AARCH64_INSN_SIZE	// ip (callsite's BL insn)
	mov	x1, x9				// parent_ip (callsite's LR)
	ldr_l	x2, function_trace_op		// op
	mov	x3, sp				// regs

SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
	bl	ftrace_stub

#ifdef CONFIG_FUNCTION_GRAPH_TRACER
SYM_INNER_LABEL(ftrace_graph_call, SYM_L_GLOBAL) // ftrace_graph_caller();
	nop				// If enabled, this will be replaced
					// "b ftrace_graph_caller"
#endif

/*
 * At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved
 * x19-x29 per the AAPCS, and we created frame records upon entry, so we need
 * to restore x0-x8, x29, and x30.
 */
ftrace_common_return:
	/* Restore function arguments */
	ldp	x0, x1, [sp]
	ldp	x2, x3, [sp, #S_X2]
	ldp	x4, x5, [sp, #S_X4]
	ldp	x6, x7, [sp, #S_X6]
	ldr	x8, [sp, #S_X8]

	/* Restore the callsite's FP, LR, PC */
	ldr	x29, [sp, #S_FP]
	ldr	x30, [sp, #S_LR]
	ldr	x9, [sp, #S_PC]

	/* Restore the callsite's SP */
	add	sp, sp, #S_FRAME_SIZE + 16

	ret	x9
SYM_CODE_END(ftrace_common)
  • ftrace_common分为两段:跳转到对应的trace回调函数前、从跳转的trace回调函数返回后。
  1. 跳转到对应的trace回调函数前:

    sub x0, x30, #AARCH64_INSN_SIZE // ip (callsite's BL insn)
    mov x1, x9 // parent_ip (callsite's LR)
    ldr_l x2, function_trace_op // op
    mov x3, sp // regs

主要是准备好给trace回调函数的参数。

参数类型大致为下面

复制代码
(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct pt_regs *regs)
  1. 后面就是进入trace的回调函数中:
    • 将传入的信息保存到trace的缓冲区中(栈帧结构体中struct pt_regs)。
    • 恢复追踪函数B的现场,x0~x8,x19~x30。(B的环境这样就没有被破坏)
    • BL 到 函数B继续执行(此时x30lr寄存器值为trace回调函数地址)。
    • 保存x0到对应的栈帧结构体中struct pt_regs的x0成员变量(函数返回值)。
    • 返回到ftrace_commonftrace_common_return继续执行。

3.ftrace_common_return

* 这里的任务是恢复现场(通过保存的栈帧结构体struct pt_regs)。

* 通过ret指令直接跳转到函数A。

  1. 说明:
    • 为什么要组织FP_N、FP_B的帧记录(即存放上一个函数的FP、LR),目的就是给具体的trace回调函数函数调用的信息,使trace回调函数能够递归函数调用关系。
    • 在用栈进行参数传递时,被调用者都是用调用者的FP指针进行访问的。

nop的跳转流程

nop的跳转流程和_mcount差不多,差别就是栈的设置以及保存,恢复,以及进入bl ftrace的时机不同。

具体的ftrace操作

见文章《Linux-ftrace(内核调试工具)》

相关推荐
夜月yeyue29 分钟前
静态库与动态库简介
linux·c++·stm32·单片机·嵌入式硬件
程序员JerrySUN1 小时前
驱动开发硬核特训 │ Day 23(下篇): i.MX8MP LCDIFv3 驱动中的 Regulator 系统全解
linux·驱动开发·嵌入式硬件
我真不会起名字啊1 小时前
每日Bug:(2)共享内存
linux·运维·bug
2401_897930062 小时前
Neo4j 的 `SET n += $properties` 语法详解
linux·服务器·neo4j
hnlucky2 小时前
Docker 获取 Python 镜像操作指南
linux·运维·python·docker·容器·centos
liuyunluoxiao3 小时前
磁盘文件系统【Linux操作系统】
linux
加油,旭杏3 小时前
【Linux】环境基础开发工具使用
linux·运维·服务器
遇见火星4 小时前
Linux安装部署Postgresql数据库
linux·数据库·postgresql
smileNicky4 小时前
RabbitMQ Linux 安装教程详解
linux·分布式·rabbitmq
板鸭〈小号〉4 小时前
Linux进程控制
linux·运维·服务器