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);