andorid使能ftrace失败

Function graph配置失败

配置依赖

在kernel/trace/Kconfig文件中可知,FUNCTION_GRAPH_TRACER依赖于HAVE_FUNCTION_GRAPH_TRACER、FUNCTION_TRACER,在arm64架构里面,当两者均满足时,便会自动设置FUNCTION_GRAPH_TRACER

config HAVE_FUNCTION_GRAPH_TRACER
        bool
        help
          See Documentation/trace/ftrace-design.rst
          
config FUNCTION_TRACER
        bool "Kernel Function Tracer"
        depends on HAVE_FUNCTION_TRACER
        select KALLSYMS
        select GENERIC_TRACER
        select CONTEXT_SWITCH_TRACER
        select GLOB
        select TASKS_RCU if PREEMPTION
        help
          Enable the kernel to trace every kernel function. This is done
          by using a compiler feature to insert a small, 5-byte No-Operation
          instruction at the beginning of every kernel function, which NOP
          sequence is then dynamically patched into a tracer call when
          tracing is enabled by the administrator. If it's runtime disabled
          (the bootup default), then the overhead of the instructions is very
          small and not measurable even in micro-benchmarks.        

config FUNCTION_GRAPH_TRACER
        bool "Kernel Function Graph Tracer"
        depends on HAVE_FUNCTION_GRAPH_TRACER
        depends on FUNCTION_TRACER
        depends on !X86_32 || !CC_OPTIMIZE_FOR_SIZE
        default y
        help
          Enable the kernel to trace a function at both its return
          and its entry.
          Its first purpose is to trace the duration of functions and
          draw a call graph for each thread with some information like
          the return value. This is done by setting the current return
          address on the current task structure into a stack of calls.

从实际编译出来的config文件来看,FUNCTION_TRACER已经被配置使能,但是FUNCTION_GRAPH_TRACER没有被使能,根据前面宏依赖的描述,则判断应该是HAVE_FUNCTION_GRAPH_TRACER没有被使能。查看HAVE_FUNCTION_GRAPH_TRACER的依赖配置,可以看到HAVE_FUNCTION_GRAPH_TRACER依赖于SHADOW_CALL_STACK:

//arm64/Kconfig
config ARM64
        def_bool y
        select HAVE_DYNAMIC_FTRACE
        select HAVE_EFFICIENT_UNALIGNED_ACCESS
        select HAVE_FAST_GUP
        select HAVE_FTRACE_MCOUNT_RECORD
        select HAVE_FUNCTION_TRACER
        select HAVE_FUNCTION_ERROR_INJECTION
        select HAVE_FUNCTION_GRAPH_TRACER if !SHADOW_CALL_STACK
        select ARCH_SUPPORTS_SHADOW_CALL_STACK if CC_HAVE_SHADOW_CALL_STACK

根据android中编译出来的config文件(out/target/product/venus/obj/kernel/msm-5.4/.config),SHADOW_CALL_STACK宏被配置使能,因此导致HAVE_FUNCTION_GRAPH_TRACER无法被配置使能:

CONFIG_CC_HAVE_SHADOW_CALL_STACK=y
CONFIG_ARCH_SUPPORTS_SHADOW_CALL_STACK=y
CONFIG_SHADOW_CALL_STACK=y
CONFIG_SHADOW_CALL_STACK_VMAP=y

然后我们需要看下SHADOW_CALL_STACK是干啥用的。

SHADOW_CALL_STACK

shadow_call_stack是一种LLVM的插桩模式,为防止返回地址被重写(栈缓冲溢出)而实现的一种机制,可见函数返回地址保存到一个专用的内存中(ShadowCallStack)。在aarch64上,预留X18寄存器,用于保存这块专用内存的地址,函数调用时,将返回地址保存到X18指向的内存,调用返回时,通过X18寄存器读取返回地址。

ffffffc010081d98:        d10243ff         sub        sp, sp, #0x90
**ffffffc010081d9c:        f800865e         str        x30, [x18],#8**
ffffffc010081da0:        a9037bfd         stp        x29, x30, [sp,#48]
ffffffc010081da4:        a9046ffc         stp        x28, x27, [sp,#64]
ffffffc010081da8:        a90567fa         stp        x26, x25, [sp,#80]
ffffffc010081dac:        a9065ff8         stp        x24, x23, [sp,#96]
ffffffc010081db0:        a90757f6         stp        x22, x21, [sp,#112]
ffffffc010081db4:        a9084ff4         stp        x20, x19, [sp,#128]

............
ffffffc01008227c:        a9437bfd         ldp        x29, x30, [sp,#48]
ffffffc010082280:        a9484ff4         ldp        x20, x19, [sp,#128]
ffffffc010082284:        a94757f6         ldp        x22, x21, [sp,#112]
ffffffc010082288:        a9465ff8         ldp        x24, x23, [sp,#96]
ffffffc01008228c:        a94567fa         ldp        x26, x25, [sp,#80]
ffffffc010082290:        a9446ffc         ldp        x28, x27, [sp,#64]
**ffffffc010082294:        f85f8e5e         ldr        x30, [x18,#-8]!**
ffffffc010082298:        910243ff         add        sp, sp, #0x90
ffffffc01008229c:        d65f03c0         ret

对于这种保存返回地址的形式(不是保存在栈上),在进行调用栈unwind的时候,需要进行特别的兼容(LLVM的unwind已经兼容,但是其他的可能不行)。

Android 支持将 ShadowCallStack 用于内核和用户空间。目前看到的是kernel中已经配置,而用户空间没有,用户空间启动shadowcallstack时需要添加如下几行:

sanitize: {
  scs: true
}

在对用于空间的组件使能该功能时,要求和该组件交互的所有的动态库都使能该功能。虽然所有系统库在编译时都预留了X18寄存器用于ShadowCallStack,但是如果同一个进程中,使用了其他没有使用ShadowCallStack的动态库(如第三方库),则可能会破坏 X18寄存器,从而出现问题。

回过头来,看SHADOW_CALL_STACK在/arch/Kconfig中的配置依赖:

config ARCH_SUPPORTS_SHADOW_CALL_STACK
        bool
        help
          An architecture should select this if it supports Clang's Shadow
          Call Stack, has asm/scs.h, and implements runtime support for shadow
          stack switching.

config SHADOW_CALL_STACK
        bool "Clang Shadow Call Stack"
        depends on ARCH_SUPPORTS_SHADOW_CALL_STACK
        help
          This option enables Clang's Shadow Call Stack, which uses a
          shadow stack to protect function return addresses from being
          overwritten by an attacker. More information can be found from
          Clang's documentation:

            https://clang.llvm.org/docs/ShadowCallStack.html

          Note that security guarantees in the kernel differ from the ones
          documented for user space. The kernel must store addresses of shadow
          stacks used by other tasks and interrupt handlers in memory, which
          means an attacker capable reading and writing arbitrary memory may
          be able to locate them and hijack control flow by modifying shadow
          stacks that are not currently in use.

config SHADOW_CALL_STACK_VMAP
        bool "Use virtually mapped shadow call stacks"
        depends on SHADOW_CALL_STACK
        default y
        help
          Use virtually mapped shadow call stacks. Selecting this option
          provides better stack exhaustion protection, but increases per-thread
          memory consumption as a full page is allocated for each shadow stack. 

在arm64/Kconfig中有定义ARCH_SUPPORTS_SHADOW_CALL_STACK的依赖:

# Supported by clang >= 7.0
config CC_HAVE_SHADOW_CALL_STACK
        def_bool $(cc-option, -fsanitize=shadow-call-stack -ffixed-x18)

config ARM64
        def_bool y
        select ARCH_SUPPORTS_SHADOW_CALL_STACK if CC_HAVE_SHADOW_CALL_STACK

cc-option将检查编译器是否支持-fsanitize=shadow-call-stack -ffixed-x18编译选项,这个在该行的注释行中也可以看到,当clang >= 7.0的时候就支持该编译选项了,所以CC_HAVE_SHADOW_CALL_STACK将会被配置使能,从而也会配置ARCH_SUPPORTS_SHADOW_CALL_STACK、SHADOW_CALL_STACK了。

也就是,funtion graph trace在当前系统中无法开启使用

Function trace失败

在配置好function_tracer之后cat available_filter_functions时显示no such device的错误,查看dmesg log,发现有ftrace的warning打印:

[    0.000000] ftrace: allocating 55017 entries in 215 pages
[    0.000000] ------------[ cut here ]------------
[    0.000000] WARNING: CPU: 0 PID: 0 at kernel/trace/ftrace.c:2010 ftrace_bug+0x9c/0x38c
[    0.000000] Modules linked in:
[    0.000000] CPU: 0 PID: 0 Comm: swapper Not tainted 5.4.86-g7715c8f3a845-dirty #14
[    0.000000] pstate: 60400085 (nZCv daIf +PAN -UAO)
[    0.000000] pc : ftrace_bug+0x9c/0x38c
[    0.000000] lr : ftrace_process_locs+0x318/0x3d8
[    0.000000] sp : ffffffe3e4773ef0
[    0.000000] x29: ffffffe3e4773f00 x28: 0000000000000001 
[    0.000000] x27: ffffff800d71db80 x26: 0000000000000000 
[    0.000000] x25: ffffff800d780008 x24: ffffffe3e4787000 
[    0.000000] x23: 0000000000000000 x22: 0000000000000008 
[    0.000000] x21: ffffffe3e2f3ae6c x20: ffffffe3e1881dc0 
[    0.000000] x19: ffffff800d780000 x18: ffffffe3e4848020 
[    0.000000] x17: ffffffe3e49ce6d8 x16: ffffffe3e49ce670 
[    0.000000] x15: ffffffe3e49ce608 x14: ffffffe3e49ce5a0 
[    0.000000] x13: 0000000000000000 x12: ffffffe3e2f3d608 
[    0.000000] x11: ffffffe3e4787000 x10: ffffffe3e4787000 
[    0.000000] x9 : 0000000000000001 x8 : ffffffe3e4787000 
[    0.000000] x7 : 0000000000000000 x6 : ffffffe3e4773ed8 
[    0.000000] x5 : ffffffe3e4773ed8 x4 : 0000000000000001 
[    0.000000] x3 : 0000000094000d58 x2 : 0000000000000004 
[    0.000000] x1 : ffffff800d780000 x0 : ffffffe3e3950a13 
[    0.000000] Call trace:
[    0.000000] ftrace_bug+0x9c/0x38c
[    0.000000] ftrace_process_locs+0x318/0x3d8
[    0.000000] ftrace_init+0xac/0xe8
[    0.000000] start_kernel+0x188/0x424
[    0.000000] ---[ end trace 46f5f85ff75319ac ]---
[    0.000000] ftrace failed to modify 
[    0.000000] [<ffffffe3e1881dc0>] __do_softirq+0x28/0x510

根据代码逻辑,当打开available_tracer的proc节点时,会判断全局变量ftrace_disabled的值,当该值为1时,会报出no such device的错误,即-ENODEV的错误码。

static int
ftrace_avail_open(struct inode *inode, struct file *file)
{
        struct ftrace_iterator *iter;
        int ret;

        ret = security_locked_down(LOCKDOWN_TRACEFS);
        if (ret)
                return ret;

        if (unlikely(ftrace_disabled))
                return -ENODEV;
}

置ftrace_disabled变量为1的地方有两处:

void __init ftrace_init(void)
{
        extern unsigned long __start_mcount_loc[];
        extern unsigned long __stop_mcount_loc[];
        unsigned long count, flags;
        int ret;

        local_irq_save(flags);
        ret = ftrace_dyn_arch_init();
        local_irq_restore(flags);
        if (ret)
                goto failed;

        count = __stop_mcount_loc - __start_mcount_loc;
        if (!count) {
                pr_info("ftrace: No functions to be traced?\n");
                goto failed;
        }

        pr_info("ftrace: allocating %ld entries in %ld pages\n",
                count, count / ENTRIES_PER_PAGE + 1);

        last_ftrace_enabled = ftrace_enabled = 1;

        ret = ftrace_process_locs(NULL,
                                  __start_mcount_loc,
                                  __stop_mcount_loc);

        set_ftrace_early_filters();

        return;
 failed:
        ftrace_disabled = 1;
}

结合dmsg log和代码流程来看,ftrace_disabled是在如下函数中置位的:

#define FTRACE_WARN_ON(cond)                        \
        ({                                        \
                int ___r = cond;                \
                if (WARN_ON(___r))                \
                        ftrace_kill();                \
                ___r;                                \
        })

#define FTRACE_WARN_ON_ONCE(cond)                \
        ({                                        \
                int ___r = cond;                \
                if (WARN_ON_ONCE(___r))                \
                        ftrace_kill();                \
                ___r;                                \
        })
        
void ftrace_kill(void)
{
       ftrace_disabled = 1;
       ftrace_enabled = 0;
       ftrace_trace_function = ftrace_stub;
}

void ftrace_bug(int failed, struct dyn_ftrace *rec)
{
        unsigned long ip = rec ? rec->ip : 0;

        switch (failed) {
        case -EFAULT:
                FTRACE_WARN_ON_ONCE(1);
                pr_info("ftrace faulted on modifying ");
                print_ip_sym(ip);
                break;
        case -EINVAL:
                FTRACE_WARN_ON_ONCE(1);
                pr_info("ftrace failed to modify ");
                print_ip_sym(ip);
                print_ip_ins(" actual:   ", (unsigned char *)ip);
                pr_cont("\n");

然后根据backtrace发现是在函数ftrace_process_locs中出现的bug,该函数用于给待跟踪的各个函数进行插装,将bl mcount的指令码替换为nop指令。此时替换出错的函数为:

[    0.000000] Call trace:
[    0.000000] ftrace_bug+0x9c/0x38c
[    0.000000] ftrace_process_locs+0x318/0x3d8
[    0.000000] ftrace_init+0xac/0xe8
[    0.000000] start_kernel+0x188/0x424
[    0.000000] ---[ end trace 46f5f85ff75319ac ]---
[    0.000000] ftrace failed to modify 
[    0.000000] [<ffffffe3e1881dc0>] __do_softirq+0x28/0x510

也就是如下的需要修改pc = ffffffc010081dc0处的指令码94000d58。

ffffffc010081d98 <__do_softirq>:
__do_softirq():
/kernel/softirq.c:251
ffffffc010081d98:        d10243ff         sub        sp, sp, #0x90
ffffffc010081d9c:        f800865e         str        x30, [x18],#8
ffffffc010081da0:        a9037bfd         stp        x29, x30, [sp,#48]
ffffffc010081da4:        a9046ffc         stp        x28, x27, [sp,#64]
ffffffc010081da8:        a90567fa         stp        x26, x25, [sp,#80]
ffffffc010081dac:        a9065ff8         stp        x24, x23, [sp,#96]
ffffffc010081db0:        a90757f6         stp        x22, x21, [sp,#112]
ffffffc010081db4:        a9084ff4         stp        x20, x19, [sp,#128]
ffffffc010081db8:        9100c3fd         add        x29, sp, #0x30
ffffffc010081dbc:        aa1e03f8         mov        x24, x30
ffffffc010081dc0:        94000d58         bl        ffffffc010085320 <_mcount>

根据vmlinux的汇编,发现错误出现在如下函数:

static int
ftrace_code_disable(struct module *mod, struct dyn_ftrace *rec)
{
        int ret;

  if (unlikely(ftrace_disabled))
                return 0;

        ret = ftrace_make_nop(mod, rec, MCOUNT_ADDR);
        if (ret) {
                ftrace_bug_type = FTRACE_BUG_INIT;
                ftrace_bug(ret, rec);
                return 0;
        }
        return 1;
}

int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
                    unsigned long addr)
{
        unsigned long pc = rec->ip;
        bool validate = true;
        u32 old = 0, new;
        long offset = (long)pc - (long)addr;

        if (offset < -SZ_128M || offset >= SZ_128M) {
#ifdef CONFIG_ARM64_MODULE_PLTS
                u32 replaced;

                /*
                 * 'mod' is only set at module load time, but if we end up
                 * dealing with an out-of-range condition, we can assume it
                 * is due to a module being loaded far away from the kernel.
                 */
                if (!mod) {
                        preempt_disable();
                        mod = __module_text_address(pc);
                        preempt_enable();

                        if (WARN_ON(!mod))
                                return -EINVAL;
                }

                /*
                 * The instruction we are about to patch may be a branch and
                 * link instruction that was redirected via a PLT entry. In
                 * this case, the normal validation will fail, but we can at
                 * least check that we are dealing with a branch and link
                 * instruction that points into the right module.
                 */
                if (aarch64_insn_read((void *)pc, &replaced))
                        return -EFAULT;

                if (!aarch64_insn_is_bl(replaced) ||
                    !within_module(pc + aarch64_get_branch_offset(replaced),
                                   mod))
                        return -EINVAL;

                validate = false;
#else /* CONFIG_ARM64_MODULE_PLTS */
                return -EINVAL;
#endif /* CONFIG_ARM64_MODULE_PLTS */
        } else {
        //通过计算偏移计算出来的旧的指令码
                old = aarch64_insn_gen_branch_imm(pc, addr,
                                                  AARCH64_INSN_BRANCH_LINK);
        }

        new = aarch64_insn_gen_nop(); //新的指令码
        return ftrace_modify_code(pc, old, new, validate);  //修改pc处的指令码
}


static int ftrace_modify_code(unsigned long pc, u32 old, u32 new,
                              bool validate)
{
        u32 replaced;

        /*
         * Note:
         * We are paranoid about modifying text, as if a bug were to happen, it
         * could cause us to read or write to someplace that could cause harm.
         * Carefully read and modify the code with aarch64_insn_*() which uses
         * probe_kernel_*(), and make sure what we read is what we expected it
         * to be before modifying it.
         */
        if (validate) {
                if (aarch64_insn_read((void *)pc, &replaced)) //读取pc处的指令码
                        return -EFAULT;

                if (replaced != old) //如果pc处的指令码和旧的指令码不相同,则返回错误
                        return -EINVAL;
        }
        if (aarch64_insn_patch_text_nosync((void *)pc, new))
                return -EPERM;

        return 0;
}

根据添加的log打印,发现此时pc处的指令码和旧的指令码是不同的,导致主动报错:

[    0.000000] ftrace:0 code update success
[    0.000000] ftrace:ftrace_code_disable mod MCOUNT_ADDR is ffffffe9ce53ae6c
[    0.000000] ftrace:ftrace_code_disable mod _mcount is ffffffe9ce53ae6c
[    0.000000] ftrace: before ftrace_modify_code
[    0.000000] ftrace:aarch64_insn_gen_branch_imm pc is ffffffe9cce81dc0 addr is ffffffe9ce53ae6c
[    0.000000] ftrace: replaced is **94000d58** while old is **945ae42b** and validate is 1

bl跳转指令对应的指令码为94,后面的为跳转偏移量(4字节对齐),aarch64_insn_gen_branch_imm根据当前pc值和目标地址addr计算出pc处的指令码:

94000000 | ((addr - pc)>> 2) 
= 94000000 | ((ffffffe9ce53ae6c - ffffffe9cce81dc0)>> 2 = 16B90AC >> 2 )
= 94000000 | 5ae42b = 945ae42b

从这里看,旧的指令码计算没有错,那为什么会导致和PC处的指令码不等的情况呢?看此时的汇编

ffffffc010081dc0:        94000d58         bl        ffffffc010085320 <_mcount>

按照这里的汇编,同样根据aarch64_insn_gen_branch_imm的计算方式得到:

94000000 | ((addr - pc)>> 2) 
= 94000000 | ((ffffffc010085320 - ffffffc010081dc0)>> 2 = 3560 >> 2 )
= 94000000 | d58 = 94000d58

这里计算的也没有错。在System.map中搜索_mcount的时候发现有一个变量_mcount.cfi_jt,利用该变量的地址进行计算,发现其结果为945ae42b,和代码运行过程中计算出来的指令码是一致的。

通过查阅可知,_mcount.cfi_jt是由于LLVM的control-flow integrity(CFI)机制产生的一个变量(extern 类型的全局变量都会有附带的一个cfi_jt后缀的变量)。_mcount.cfi_jt的地址和_mcount的地址不同,_mcount.cfi_jt的内容为_mcount的地址,即_mcount.cfi_jt相当于一个二级指针。这就导致ftrace_modify_code函数中由于使用_mcount.cfi_jt的地址计算出来的指令码与pc处读取出来的指令码不匹配而造成ftrace_init失败。可根据vmlinux来进行验证,如下是ftrace_code_disable的源码,其中MCOUNT_ADDR就是_mcount的地址,该函数意在将将bl _mcount修改为nop:

static int ftrace_code_disable(struct module *mod, struct dyn_ftrace *rec)
{
        int ret;

        if (unlikely(ftrace_disabled))
                return 0;

        ret = ftrace_make_nop(mod, rec, MCOUNT_ADDR); 
        if (ret) {
                ftrace_bug_type = FTRACE_BUG_INIT;
                ftrace_bug(ret, rec);
                return 0;
        }
        return 1;
}

根据代码,查看MCOUNT_ADDR的定义为:

#define MCOUNT_ADDR                ((unsigned long)_mcount)

而_mcount为一个地址,其值为ffffffc010085320:

ffffffc010085320 <_mcount>:
_mcount():
arch/arm64/kernel/entry-ftrace.S:131
ffffffc010085320:        d65f03c0         ret

而在ftrace_code_disable函数中,根据汇编:

/kernel/trace/ftrace.c:2942
ffffffc0117157a8:        b8696900         ldr        w0, [x8,x9]
ffffffc0117157ac:        97b4940f         bl        ffffffc01043a7e8 <ftrace_now>
/kernel/trace/ftrace.c:2955
ffffffc0117157b0:        f100027f         cmp        x19, #0x0
ffffffc0117157b4:        d0000135         adrp        x21, ffffffc01173b000 <regulator_get_current_limit_regmap.cfi_jt>
ffffffc0117157b8:        1a9f07e8         cset        w8, ne
ffffffc0117157bc:        aa1f03f4         mov        x20, xzr
ffffffc0117157c0:        d000c398         adrp        x24, ffffffc012f87000 <arpfilter_ops>
ffffffc0117157c4:        913a52b5         add        x21, x21, #0xe94
ffffffc0117157c8:        d367991a         lsl        x26, x8, #25
ftrace_process_locs():
ffffffc0117157cc:        5280003c         mov        w28, #0x1                           // #1
ffffffc0117157d0:        14000008         b        ffffffc0117157f0 <ftrace_process_locs+0x324>
ftrace_code_disable():
/kernel/trace/ftrace.c:2528
ffffffc0117157d4:        d000ef28         adrp        x8, ffffffc0134fb000 <kretprobe_table_locks+0xe80>
/kernel/trace/ftrace.c:2529
ffffffc0117157d8:        d1002321         sub        x1, x25, #0x8
/kernel/trace/ftrace.c:2528
ffffffc0117157dc:        b9022d1c         str        w28, [x8,#556]
/kernel/trace/ftrace.c:2529
ffffffc0117157e0:        97b45766         bl        ffffffc01042b578 <ftrace_bug>
ftrace_process_locs():
ffffffc0117157e4:        8b170294         add        x20, x20, x23
ftrace_update_code():
/kernel/trace/ftrace.c:2958
ffffffc0117157e8:        f940037b         ldr        x27, [x27]
ffffffc0117157ec:        b40002db         cbz        x27, ffffffc011715844 <ftrace_process_locs+0x378>
/kernel/trace/ftrace.c:2960
ffffffc0117157f0:        b9401368         ldr        w8, [x27,#16]
ffffffc0117157f4:        7100051f         cmp        w8, #0x1
ffffffc0117157f8:        54ffff8b         b.lt        ffffffc0117157e8 <ftrace_process_locs+0x31c>
ftrace_process_locs():
ffffffc0117157fc:        aa1f03f7         mov        x23, xzr
ffffffc011715800:        52800116         mov        w22, #0x8                           // #8
ftrace_update_code():
/kernel/trace/ftrace.c:2963
ffffffc011715804:        39701308         ldrb        w8, [x24,#3076]
ffffffc011715808:        37000308         tbnz        w8, #0, ffffffc011715868 <ftrace_process_locs+0x39c>
/kernel/trace/ftrace.c:2966
ffffffc01171580c:        f9400768         ldr        x8, [x27,#8]
ftrace_code_disable():
/kernel/trace/ftrace.c:2526
ffffffc011715810:        aa1303e0         mov        x0, x19
ffffffc011715814:        aa1503e2         mov        x2, x21
ftrace_update_code():
/kernel/trace/ftrace.c:2967
ffffffc011715818:        8b160119         add        x25, x8, x22
ffffffc01171581c:        aa1903e1         mov        x1, x25
ffffffc011715820:        f81f843a         str        x26, [x1],#-8
ftrace_code_disable():
/kernel/trace/ftrace.c:2526
ffffffc011715824:        97afa0ac         bl        ffffffc0102fdad4 <ftrace_make_nop>

其中,X2的值即为MCOUNT_ADDR,来源于X21 = ffffffc01173b000 + 0xe94 = fffffffc01173be94,该地址对应的内容如下:
ffffffc01173be94 <_mcount.cfi_jt>:

_mcount.cfi_jt():
ffffffc01173be94:        17a52523         b        ffffffc010085320 <_mcount>

从上面可以看到,MCOUNT_ADDR实际的值为_mcount.cfi_jt的值,并不是_mcount的值,其内容为跳转到_mcount的指令码。为了让MCOUNT_ADDR的值就为准确的_mcount的值,可以做如下修改:

arch/arm64/include/asm/ftrace.h中
#define function_nocfi(x) ({                                           \
       void *addr;                                                     \
       asm("adrp %0, " __stringify(x) "\n\t"                           \
           "add  %0, %0, :lo12:" __stringify(x) : "=r" (addr));        \
       addr;                                                           \
})

#define MCOUNT_ADDR                ((unsigned long)function_nocfi(_mcount))

修改之后,没有了如上log的报错,但是function的的tracer还是没能够运行起来,没有能够跟踪到具体的函数。这是由于在ftrace_update_ftrace_func函数中同样有一个由于CFI机制导致的异常:

ffffffc0102fd8c4 <ftrace_update_ftrace_func>:
ftrace_update_ftrace_func():
/arch/arm64/kernel/ftrace.c:54
ffffffc0102fd8c4:        f800865e         str        x30, [x18],#8
ffffffc0102fd8c8:        a9be7bfd         stp        x29, x30, [sp,#-32]!
ffffffc0102fd8cc:        f9000bf3         str        x19, [sp,#16]
ffffffc0102fd8d0:        910003fd         mov        x29, sp
/arch/arm64/kernel/ftrace.c:59
ffffffc0102fd8d4:        b000a273         adrp        x19, ffffffc01174a000 <ipa3_handle_gsi_differ_irq$a6ac87bcb8155c59cefa940049f930e3.cfi_jt>
ffffffc0102fd8d8:        9123a273         add        x19, x19, #0x8e8
ffffffc0102fd8dc:        aa0003e1         mov        x1, x0
ffffffc0102fd8e0:        52800022         mov        w2, #0x1                           // #1
ffffffc0102fd8e4:        aa1303e0         mov        x0, x19
ffffffc0102fd8e8:        9450851b         bl        ffffffc01171ed54 <aarch64_insn_gen_branch_imm>

ffffffc01174a8e8 <ftrace_call.cfi_jt>:
ftrace_call.cfi_jt():
ffffffc01174a8e8:        17a4ea94         b        ffffffc010085338 <ftrace_call>

使用function_nocfi去除CFI机制对其的影响,

--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -55,7 +55,7 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
 	unsigned long pc;
 	u32 new;
 
-	pc = (unsigned long)&ftrace_call;
+	pc = (unsigned long)function_nocfi(ftrace_call);

对比汇编来看:

ffffffc0102fd8c4 <ftrace_update_ftrace_func>:
ftrace_update_ftrace_func():
/arch/arm64/kernel/ftrace.c:54
ffffffc0102fd8c4:        f800865e         str        x30, [x18],#8
ffffffc0102fd8c8:        a9bd7bfd         stp        x29, x30, [sp,#-48]!
ffffffc0102fd8cc:        f9000bf5         str        x21, [sp,#16]
ffffffc0102fd8d0:        a9024ff4         stp        x20, x19, [sp,#32]
ffffffc0102fd8d4:        910003fd         mov        x29, sp
ffffffc0102fd8d8:        aa0003f3         mov        x19, x0
/arch/arm64/kernel/ftrace.c:58
ffffffc0102fd8dc:        90ffec54         adrp        x20, ffffffc010085000 <fpsimd_load_state+0x34>
ffffffc0102fd8e0:        910ce294         add        x20, x20, #0x338
/arch/arm64/kernel/ftrace.c:59
ffffffc0102fd8e4:        52800022         mov        w2, #0x1                           // #1
ffffffc0102fd8e8:        aa1403e0         mov        x0, x20
ffffffc0102fd8ec:        aa1303e1         mov        x1, x19
ffffffc0102fd8f0:        9450851f         bl        ffffffc01171ed6c <aarch64_insn_gen_branch_imm>

ffffffc010085338 <ftrace_call>:
ftrace_call():
arch/arm64/kernel/entry-ftrace.S:152
ffffffc010085338:        d503201f         nop
arch/arm64/kernel/entry-ftrace.S:161
ffffffc01008533c:        a8c17bfd         ldp        x29, x30, [sp],#16
ffffffc010085340:        d65f03c0         ret

ftrace_update_ftrace_func的本意是修改ftrace_call,即nop这个地方的指令码到一个bl func,这样就可以实现插入tracer的目的。但是,当使能了CFI时,此时修改的是ftrace_call.cfi_jt(其里面的内容是b ftrace_call),并没有修改到ftrace_call,其内容仍然是原来的nop指令,如此便达不到插入tracer的目的了。

同样的,在ftrace_get_addr_new函数中,返回FTRACE_CALLER函数同样返回的并不是ftrace_caller这个函数指针,而是ftrace_caller.cfi_jt,但是这个地方并不会引起问题,是由于代码里并不存在修改ftrace_caller处的指令码这个操作,而是修改被跟踪函数处的nop,使其跳转到ftrace_caller,这里即使是由于CFI使其跳转到了ftrace_caller.cfi_jt,根据其对应的指令码也会继续跳转到ftrace_caller处,所以不会产生影响。

ffffffc01042bc68:        f00098e0         adrp        x0, ffffffc01174a000 <asymmetric_key_cleanup$5e086280179712975e6bf1c34e602ecc.cfi_jt>
ffffffc01042bc6c:        91112000         add        x0, x0, #0x448
/kernel/trace/ftrace.c:2349
ffffffc01042bc70:        a9424ff4         ldp        x20, x19, [sp,#32]
ffffffc01042bc74:        a94157f6         ldp        x22, x21, [sp,#16]
ffffffc01042bc78:        a8c37bfd         ldp        x29, x30, [sp],#48
ffffffc01042bc7c:        f85f8e5e         ldr        x30, [x18,#-8]!
ffffffc01042bc80:        d65f03c0         ret

ffffffc01174a448 <ftrace_caller.cfi_jt>:
ftrace_caller.cfi_jt():
ffffffc01174a448:        17a4ebb7         b        ffffffc010085324 <ftrace_caller>

所以整体的修改为:

diff --git a/arch/arm64/configs/vendor/xxx.config b/arch/arm64/configs/vendor/xxx.config
index 6d6075178edb..2c3968686858 100644
--- a/arch/arm64/configs/vendor/xxx.config
+++ b/arch/arm64/configs/vendor/xxx.config
@@ -323,3 +323,25 @@ CONFIG_ZRAM=y
 CONFIG_MIUI_ZRAM_MEMORY_TRACKING=y
 CONFIG_ZRAM_WRITEBACK=y
 CONFIG_VT=y
+CONFIG_NOP_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
+CONFIG_HAVE_DYNAMIC_FTRACE=y
+CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
+CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
+CONFIG_HAVE_C_RECORDMCOUNT=y
+CONFIG_TRACER_MAX_TRACE=y
+CONFIG_TRACE_CLOCK=y
+CONFIG_RING_BUFFER=y
+CONFIG_EVENT_TRACING=y
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_TRACING=y
+CONFIG_GENERIC_TRACER=y
+CONFIG_TRACING_SUPPORT=y
+CONFIG_FTRACE=y
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_HAVE_ARCH_TRACEHOOK=y
+CONFIG_DYNAMIC_FTRACE=y
+CONFIG_FUNCTION_PROFILER=y
\ No newline at end of file
diff --git a/arch/arm64/include/asm/ftrace.h b/arch/arm64/include/asm/ftrace.h
index d48667b04c41..5481c9a4967c 100644
--- a/arch/arm64/include/asm/ftrace.h
+++ b/arch/arm64/include/asm/ftrace.h
@@ -10,8 +10,16 @@
 
 #include <asm/insn.h>
 
+#define function_nocfi(x) ({                                           \
+       void *addr;                                                     \
+       asm("adrp %0, " __stringify(x) "\n\t"                           \
+           "add  %0, %0, :lo12:" __stringify(x) : "=r" (addr));        \
+       addr;                                                           \
+})
+
+
 #define HAVE_FUNCTION_GRAPH_FP_TEST
-#define MCOUNT_ADDR		((unsigned long)_mcount)
+#define MCOUNT_ADDR		((unsigned long)function_nocfi(_mcount))
 #define MCOUNT_INSN_SIZE	AARCH64_INSN_SIZE
 
 /*
diff --git a/arch/arm64/kernel/ftrace.c b/arch/arm64/kernel/ftrace.c
index 06e56b470315..75abc7437f58 100644
--- a/arch/arm64/kernel/ftrace.c
+++ b/arch/arm64/kernel/ftrace.c
@@ -55,7 +55,7 @@ int ftrace_update_ftrace_func(ftrace_func_t func)
 	unsigned long pc;
 	u32 new;
 
-	pc = (unsigned long)&ftrace_call;
+	pc = (unsigned long)function_nocfi(ftrace_call);
 	new = aarch64_insn_gen_branch_imm(pc, (unsigned long)func,
 					  AARCH64_INSN_BRANCH_LINK);

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ftrace和ptrace是两个不同的工具,它们在功能和用途上有所区别。 引用中提到的ftrace是 Linux 内核的一个内建跟踪工具,用于跟踪和分析内核函数调用、上下文切换、延迟和性能问题等。它可以通过配置内核和 debugfs 来使用,并包含多个跟踪器,可以方便地跟踪不同类型的信息。 而引用中提到的ptrace是一个系统调用,用于在用户空间中跟踪和控制进程的执行。通过ptrace,用户可以监视和修改目标进程的内存、寄存器和执行状态,实现调试和跟踪进程的功能。 因此,ftrace主要用于内核级别的跟踪和性能分析,而ptrace主要用于用户空间进程的调试和跟踪。它们各自具有不同的功能和应用场景,但都能提供有助于问题排查和性能优化的信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Linux内核调试方法总结之strace ,ltrace, ptrace, ftrace, sysrq](https://blog.csdn.net/zmjames2000/article/details/88410484)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Linux内核学习(十):内核追踪必备技能--ftrace](https://blog.csdn.net/weixin_45264425/article/details/125955998)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值