Hi,
Here is the 2nd version of the series to change the kprobes selftest
to KUnit and add testcases for stacktrace on kretprobes, which has
been fixed recently on x86. The previous version is here;
https://lore.kernel.org/all/163369609308.636038.15295764725220907794.stgit@devnote2/
In this version, I fixed some typos and coding issues according to
Will and Mark's comments. Thanks!
And I added 1 RFC patch, which will detect the unwinding error on
arm64 (just for testing) according to Mark's comment. But since
I'm not sure how to handle that error correctly in the unwinder
code. So this is just for testing.
Mark, can you tell me how can I handle it? Just asserted by WARN_ON_ONCE()
is OK? Or print out more error information? For the debugging, we need
more information, so I printed out the error code.
Thank you,
---
Masami Hiramatsu (9):
kprobes: Add a test case for stacktrace from kretprobe handler
x86/unwind: Compile kretprobe fixup code only if CONFIG_KRETPROBES=y
arm64: kprobes: Record frame pointer with kretprobe instance
arm64: kprobes: Make a frame pointer on __kretprobe_trampoline
arm64: Recover kretprobe modified return address in stacktrace
ARM: clang: Do not rely on lr register for stacktrace
ARM: kprobes: Make a frame pointer on __kretprobe_trampoline
ARM: Recover kretprobe modified return address in stacktrace
[RFC] arm64: kprobes: Detect error of kretprobe return address fixup
Sven Schnelle (1):
kprobes: convert tests to kunit
arch/Kconfig | 8 +
arch/arm/Kconfig | 1
arch/arm/include/asm/stacktrace.h | 9 +
arch/arm/kernel/return_address.c | 4
arch/arm/kernel/stacktrace.c | 17 +
arch/arm/probes/kprobes/core.c | 29 ++
arch/arm64/Kconfig | 1
arch/arm64/include/asm/stacktrace.h | 4
arch/arm64/kernel/probes/kprobes.c | 4
arch/arm64/kernel/probes/kprobes_trampoline.S | 4
arch/arm64/kernel/stacktrace.c | 13 +
arch/x86/Kconfig | 1
arch/x86/include/asm/unwind.h | 6
include/linux/kprobes.h | 2
kernel/kprobes.c | 52 +++
kernel/test_kprobes.c | 374 ++++++++++++++-----------
lib/Kconfig.debug | 3
17 files changed, 359 insertions(+), 173 deletions(-)
--
Masami Hiramatsu (Linaro) [off-list ref]
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Sven Schnelle <svens@linux.ibm.com>
This converts the kprobes testcases to use the kunit framework.
It adds a dependency on CONFIG_KUNIT, and the output will change
to TAP:
TAP version 14
1..1
# Subtest: kprobes_test
1..4
random: crng init done
ok 1 - test_kprobe
ok 2 - test_kprobes
ok 3 - test_kretprobe
ok 4 - test_kretprobes
ok 1 - kprobes_test
Note that the kprobes testcases are no longer run immediately after
kprobes initialization, but as a late initcall when kunit is
initialized. kprobes itself is initialized with an early initcall,
so the order is still correct.
Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
Acked-by: Masami Hiramatsu <mhiramat@kernel.org>
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
kernel/kprobes.c | 3 -
kernel/test_kprobes.c | 222 +++++++++++++------------------------------------
lib/Kconfig.debug | 3 -
3 files changed, 61 insertions(+), 167 deletions(-)
@@ -36,14 +32,8 @@ static int kp_pre_handler(struct kprobe *p, struct pt_regs *regs)staticvoidkp_post_handler(structkprobe*p,structpt_regs*regs,unsignedlongflags){-if(preemptible()){-handler_errors++;-pr_err("post-handler is preemptible\n");-}-if(preh_val!=(rand1/div_factor)){-handler_errors++;-pr_err("incorrect value in post_handler\n");-}+KUNIT_EXPECT_FALSE(current_test,preemptible());+KUNIT_EXPECT_EQ(current_test,preh_val,(rand1/div_factor));posth_val=preh_val+div_factor;}
@@ -53,30 +43,14 @@ static struct kprobe kp = {.post_handler=kp_post_handler};-staticinttest_kprobe(void)+staticvoidtest_kprobe(structkunit*test){-intret;--ret=register_kprobe(&kp);-if(ret<0){-pr_err("register_kprobe returned %d\n",ret);-returnret;-}--ret=target(rand1);+current_test=test;+KUNIT_EXPECT_EQ(test,0,register_kprobe(&kp));+target(rand1);unregister_kprobe(&kp);--if(preh_val==0){-pr_err("kprobe pre_handler not called\n");-handler_errors++;-}--if(posth_val==0){-pr_err("kprobe post_handler not called\n");-handler_errors++;-}--return0;+KUNIT_EXPECT_NE(test,0,preh_val);+KUNIT_EXPECT_NE(test,0,posth_val);}staticnoinlineu32kprobe_target2(u32value)
@@ -93,10 +67,7 @@ static int kp_pre_handler2(struct kprobe *p, struct pt_regs *regs)staticvoidkp_post_handler2(structkprobe*p,structpt_regs*regs,unsignedlongflags){-if(preh_val!=(rand1/div_factor)+1){-handler_errors++;-pr_err("incorrect value in post_handler2\n");-}+KUNIT_EXPECT_EQ(current_test,preh_val,(rand1/div_factor)+1);posth_val=preh_val+div_factor;}
@@ -106,51 +77,31 @@ static struct kprobe kp2 = {.post_handler=kp_post_handler2};-staticinttest_kprobes(void)+staticvoidtest_kprobes(structkunit*test){-intret;structkprobe*kps[2]={&kp,&kp2};+current_test=test;+/* addr and flags should be cleard for reusing kprobe. */kp.addr=NULL;kp.flags=0;-ret=register_kprobes(kps,2);-if(ret<0){-pr_err("register_kprobes returned %d\n",ret);-returnret;-}+KUNIT_EXPECT_EQ(test,0,register_kprobes(kps,2));preh_val=0;posth_val=0;-ret=target(rand1);+target(rand1);-if(preh_val==0){-pr_err("kprobe pre_handler not called\n");-handler_errors++;-}--if(posth_val==0){-pr_err("kprobe post_handler not called\n");-handler_errors++;-}+KUNIT_EXPECT_NE(test,0,preh_val);+KUNIT_EXPECT_NE(test,0,posth_val);preh_val=0;posth_val=0;-ret=target2(rand1);--if(preh_val==0){-pr_err("kprobe pre_handler2 not called\n");-handler_errors++;-}--if(posth_val==0){-pr_err("kprobe post_handler2 not called\n");-handler_errors++;-}+target2(rand1);+KUNIT_EXPECT_NE(test,0,preh_val);+KUNIT_EXPECT_NE(test,0,posth_val);unregister_kprobes(kps,2);-return0;-}#ifdef CONFIG_KRETPROBES
@@ -170,19 +118,9 @@ static int return_handler(struct kretprobe_instance *ri, struct pt_regs *regs){unsignedlongret=regs_return_value(regs);-if(preemptible()){-handler_errors++;-pr_err("kretprobe return handler is preemptible\n");-}-if(ret!=(rand1/div_factor)){-handler_errors++;-pr_err("incorrect value in kretprobe handler\n");-}-if(krph_val==0){-handler_errors++;-pr_err("call to kretprobe entry handler failed\n");-}-+KUNIT_EXPECT_FALSE(current_test,preemptible());+KUNIT_EXPECT_EQ(current_test,ret,rand1/div_factor);+KUNIT_EXPECT_NE(current_test,krph_val,0);krph_val=rand1;return0;}
@@ -193,39 +131,21 @@ static struct kretprobe rp = {.kp.symbol_name="kprobe_target"};-staticinttest_kretprobe(void)+staticvoidtest_kretprobe(structkunit*test){-intret;--ret=register_kretprobe(&rp);-if(ret<0){-pr_err("register_kretprobe returned %d\n",ret);-returnret;-}--ret=target(rand1);+current_test=test;+KUNIT_EXPECT_EQ(test,0,register_kretprobe(&rp));+target(rand1);unregister_kretprobe(&rp);-if(krph_val!=rand1){-pr_err("kretprobe handler not called\n");-handler_errors++;-}--return0;+KUNIT_EXPECT_EQ(test,krph_val,rand1);}staticintreturn_handler2(structkretprobe_instance*ri,structpt_regs*regs){unsignedlongret=regs_return_value(regs);-if(ret!=(rand1/div_factor)+1){-handler_errors++;-pr_err("incorrect value in kretprobe handler2\n");-}-if(krph_val==0){-handler_errors++;-pr_err("call to kretprobe entry handler failed\n");-}-+KUNIT_EXPECT_EQ(current_test,ret,(rand1/div_factor)+1);+KUNIT_EXPECT_NE(current_test,krph_val,0);krph_val=rand1;return0;}
@@ -236,78 +156,54 @@ static struct kretprobe rp2 = {.kp.symbol_name="kprobe_target2"};-staticinttest_kretprobes(void)+staticvoidtest_kretprobes(structkunit*test){-intret;structkretprobe*rps[2]={&rp,&rp2};+current_test=test;/* addr and flags should be cleard for reusing kprobe. */rp.kp.addr=NULL;rp.kp.flags=0;-ret=register_kretprobes(rps,2);-if(ret<0){-pr_err("register_kretprobe returned %d\n",ret);-returnret;-}+KUNIT_EXPECT_EQ(test,0,register_kretprobes(rps,2));krph_val=0;-ret=target(rand1);-if(krph_val!=rand1){-pr_err("kretprobe handler not called\n");-handler_errors++;-}+target(rand1);+KUNIT_EXPECT_EQ(test,krph_val,rand1);krph_val=0;-ret=target2(rand1);-if(krph_val!=rand1){-pr_err("kretprobe handler2 not called\n");-handler_errors++;-}+target2(rand1);+KUNIT_EXPECT_EQ(test,krph_val,rand1);unregister_kretprobes(rps,2);-return0;}#endif /* CONFIG_KRETPROBES */-intinit_test_probes(void)+staticintkprobes_test_init(structkunit*test){-intret;-target=kprobe_target;target2=kprobe_target2;do{rand1=prandom_u32();}while(rand1<=div_factor);+return0;+}-pr_info("started\n");-num_tests++;-ret=test_kprobe();-if(ret<0)-errors++;--num_tests++;-ret=test_kprobes();-if(ret<0)-errors++;-+staticstructkunit_casekprobes_testcases[]={+KUNIT_CASE(test_kprobe),+KUNIT_CASE(test_kprobes),#ifdef CONFIG_KRETPROBES-num_tests++;-ret=test_kretprobe();-if(ret<0)-errors++;--num_tests++;-ret=test_kretprobes();-if(ret<0)-errors++;-#endif /* CONFIG_KRETPROBES */+KUNIT_CASE(test_kretprobe),+KUNIT_CASE(test_kretprobes),+#endif+{}+};-if(errors)-pr_err("BUG: %d out of %d tests failed\n",errors,num_tests);-elseif(handler_errors)-pr_err("BUG: %d error(s) running handlers\n",handler_errors);-else-pr_info("passed successfully\n");+staticstructkunit_suitekprobes_test_suite={+.name="kprobes_test",+.init=kprobes_test_init,+.test_cases=kprobes_testcases,+};-return0;-}+kunit_test_suites(&kprobes_test_suite);++MODULE_LICENSE("GPL");
Record the frame pointer instead of stack address with kretprobe
instance as the identifier on the instance list.
Since arm64 always enable CONFIG_FRAME_POINTER, we can use the
actual frame pointer (x29).
This will allow the stacktrace code to find the original return
address from the FP alone.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Acked-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
---
Changes in v2:
- Update changelog according to Mark's comment.
---
arch/arm64/kernel/probes/kprobes.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
Make a frame pointer (make the x29 register points the
address of pt_regs->regs[29]) on __kretprobe_trampoline.
This frame pointer will be used by the stacktracer when it is
called from the kretprobe handlers. In this case, the stack
tracer will unwind stack to trampoline_probe_handler() and
find the next frame pointer in the stack frame of the
__kretprobe_trampoline().
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Acked-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/probes/kprobes_trampoline.S | 4 ++++
1 file changed, 4 insertions(+)
Add a test case for stacktrace from kretprobe handler and
nested kretprobe handlers.
This test checks both of stack trace inside kretprobe handler
and stack trace from pt_regs. Those stack trace must include
actual function return address instead of kretprobe trampoline.
The nested kretprobe stacktrace test checks whether the unwinder
can correctly unwind the call frame on the stack which has been
modified by the kretprobe.
Since the stacktrace on kretprobe is correctly fixed only on x86,
this introduces a meta kconfig ARCH_CORRECT_STACKTRACE_ON_KRETPROBE
which tells user that the stacktrace on kretprobe is correct or not.
The test results will be shown like below;
TAP version 14
1..1
# Subtest: kprobes_test
1..6
ok 1 - test_kprobe
ok 2 - test_kprobes
ok 3 - test_kretprobe
ok 4 - test_kretprobes
ok 5 - test_stacktrace_on_kretprobe
ok 6 - test_stacktrace_on_nested_kretprobe
# kprobes_test: pass:6 fail:0 skip:0 total:6
# Totals: pass:6 fail:0 skip:0 total:6
ok 1 - kprobes_test
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
arch/Kconfig | 8 ++
arch/x86/Kconfig | 1
kernel/test_kprobes.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 167 insertions(+)
@@ -58,6 +63,33 @@ static noinline u32 kprobe_target2(u32 value)return(value/div_factor)+1;}+staticnoinlineunsignedlongkprobe_stacktrace_internal_target(void)+{+if(!target_return_address[0])+target_return_address[0]=(unsignedlong)__builtin_return_address(0);+returntarget_return_address[0];+}++staticnoinlineunsignedlongkprobe_stacktrace_target(void)+{+if(!target_return_address[1])+target_return_address[1]=(unsignedlong)__builtin_return_address(0);++if(internal_target)+internal_target();++returntarget_return_address[1];+}++staticnoinlineunsignedlongkprobe_stacktrace_driver(void)+{+if(stacktrace_target)+stacktrace_target();++/* This is for preventing inlining the function */+return(unsignedlong)__builtin_return_address(0);+}+staticintkp_pre_handler2(structkprobe*p,structpt_regs*regs){preh_val=(rand1/div_factor)+1;
Since the kretprobe replaces the function return address with
the kretprobe_trampoline on the stack, stack unwinder shows it
instead of the correct return address.
This checks whether the next return address is the
__kretprobe_trampoline(), and if so, try to find the correct
return address from the kretprobe instance list. For this purpose
this adds 'kr_cur' loop cursor to memorize the current kretprobe
instance.
With this fix, now arm64 can enable
CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE, and pass the
kprobe self tests.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
Changes in v2:
- Add comment for kr_cur.
- Make the kretprobe related code depends on CONFIG_KRETPROBES.
- Initialize "kr_cur" directly in start_backtrace() instead
of clearing "frame" data structure by memset().
---
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/stacktrace.h | 4 ++++
arch/arm64/kernel/stacktrace.c | 7 +++++++
3 files changed, 12 insertions(+)
Currently the stacktrace on clang compiled arm kernel uses the 'lr'
register to find the first frame address from pt_regs. However, that
is wrong after calling another function, because the 'lr' register
is used by 'bl' instruction and never be recovered.
As same as gcc arm kernel, directly use the frame pointer (r11) of
the pt_regs to find the first frame address.
Note that this fixes kretprobe stacktrace issue only with
CONFIG_UNWINDER_FRAME_POINTER=y. For the CONFIG_UNWINDER_ARM,
we need another fix.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
---
Changes in v2:
- Fix typos in changelog.
---
arch/arm/kernel/stacktrace.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
@@ -54,8 +54,7 @@ int notrace unwind_frame(struct stackframe *frame)frame->sp=frame->fp;frame->fp=*(unsignedlong*)(fp);-frame->pc=frame->lr;-frame->lr=*(unsignedlong*)(fp+4);+frame->pc=*(unsignedlong*)(fp+4);#else/* check current frame pointer is within bounds */if(fp<low+12||fp>high-4)
Currently kretprobe on ARM just fills r0-r11 of pt_regs, but
that is not enough for the stacktrace. Moreover, from the user
kretprobe handler, stacktrace needs a frame pointer on the
__kretprobe_trampoline.
This adds a frame pointer on __kretprobe_trampoline for both gcc
and clang case. Those have different frame pointer so we need
different but similar stack on pt_regs.
Gcc makes the frame pointer (fp) to point the 'pc' address of
the {fp, ip (=sp), lr, pc}, this means {r11, r13, r14, r15}.
Thus if we save the r11 (fp) on pt_regs->r12, we can make this
set on the end of pt_regs.
On the other hand, Clang makes the frame pointer to point the
'fp' address of {fp, lr} on stack. Since the next to the
pt_regs->lr is pt_regs->sp, I reused the pair of pt_regs->fp
and pt_regs->ip.
So this stores the 'lr' on pt_regs->ip and make the fp to point
pt_regs->fp.
For both cases, saves __kretprobe_trampoline address to
pt_regs->lr, so that the stack tracer can identify this frame
pointer has been made by the __kretprobe_trampoline.
Note that if the CONFIG_FRAME_POINTER is not set, this keeps
fp as is.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
---
arch/arm/probes/kprobes/core.c | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
Since the kretprobe replaces the function return address with
the kretprobe_trampoline on the stack, arm unwinder shows it
instead of the correct return address.
This finds the correct return address from the per-task
kretprobe_instances list and verify it is in between the
caller fp and callee fp.
Note that this supports both GCC and clang if CONFIG_FRAME_POINTER=y
and CONFIG_ARM_UNWIND=n. For the ARM unwinder, this is still
not working correctly.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
Changes in v2:
- Compile this code only when CONFIG_KRETPROBES=y
---
arch/arm/Kconfig | 1 +
arch/arm/include/asm/stacktrace.h | 9 +++++++++
arch/arm/kernel/return_address.c | 4 ++++
arch/arm/kernel/stacktrace.c | 14 ++++++++++++++
4 files changed, 28 insertions(+)
Add kretprobe_next_ret_addr() which can detect errors in
the given parameter or the kretprobe_instance list, and call
it from arm64 stacktrace.
This kretprobe_next_ret_addr() will return following errors
when it detects;
- -EINVAL if @cur is NULL (caller issue)
- -ENOENT if there is no next correct return address
(either kprobes or caller issue)
- -EILSEQ if the next currect return address is there
but doesn't match the framepointer (maybe caller issue)
Thus the caller must check the error and handle it. On arm64,
this tries to handle the errors and show it on the log.
Suggested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
---
arch/arm64/kernel/stacktrace.c | 10 +++++++-
include/linux/kprobes.h | 2 ++
kernel/kprobes.c | 49 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 2 deletions(-)
@@ -133,8 +133,14 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)}#endif /* CONFIG_FUNCTION_GRAPH_TRACER */#ifdef CONFIG_KRETPROBES-if(is_kretprobe_trampoline(frame->pc))-frame->pc=kretprobe_find_ret_addr(tsk,(void*)frame->fp,&frame->kr_cur);+if(is_kretprobe_trampoline(frame->pc)){+void*ret=kretprobe_next_ret_addr(tsk,(void*)frame->fp,&frame->kr_cur);+/* There must be a bug in this unwinder or kretprobe. */+if(WARN_ON_ONCE(IS_ERR(ret)))+pr_err("Kretprobe_trampoline recovery failed (%d)\n",PTR_ERR(ret));+else+frame->pc=(unsignedlong)ret;+}#endifframe->pc=ptrauth_strip_insn_pac(frame->pc);
@@ -1922,6 +1922,55 @@ unsigned long kretprobe_find_ret_addr(struct task_struct *tsk, void *fp,}NOKPROBE_SYMBOL(kretprobe_find_ret_addr);+/**+*kretprobe_next_ret_addr--Findnextcorrectreturnaddressfrom@cur+*@tsk:Targettask+*@fp:Aframepointertoverify+*@cur:astorageandthebasepointoftheloopcursor.+*+*Findthenextcorrectreturnaddressmodifiedbyakretprobeon@tskfrom+*theentrywhichpoints*@cur.Ifitfindsthenextcurrectreturnaddress+*whoseframepointermatches@fp,returnsthereturnaddress.+*Ifthenextcurrentreturnaddress'sframepointerdoesn'tmatch@fp,this+*returnsERR_PTR(-EILSEQ).Ifthe*@curistheendofthekretprobe_instance+*list,returnsERR_PTR(-ENOENT).Ifthe@curisNULL,returnsERR_PTR(-EINVAL).+*The@tskmustbe'current'orataskwhichisnotrunning.@fpisusedfor+*verifyingtheframepointerwhichrecordedwiththecorrectreturnaddress+*(kretprobe_instance::fpfield.)+*The@curisaloopcursorforsearchingthekretprobereturnaddresseson+*the@tsk.If*@curisNULL,thisreturnsthetopentryofthecorrectreturn+*address.+*/+kprobe_opcode_t*kretprobe_next_ret_addr(structtask_struct*tsk,void*fp,+structllist_node**cur)+{+structkretprobe_instance*ri=NULL;+kprobe_opcode_t*ret;++if(WARN_ON_ONCE(!cur))+returnERR_PTR(-EINVAL);++if(*cur){+/* This returns the next correct return address */+ret=__kretprobe_find_ret_addr(tsk,cur);+if(!ret)+returnERR_PTR(-ENOENT);+ri=container_of(*cur,structkretprobe_instance,llist);+returnri->fp==fp?ret:ERR_PTR(-EILSEQ);+}++/* If this is the first try, find the FP-matched entry */+do{+ret=__kretprobe_find_ret_addr(tsk,cur);+if(!ret)+returnERR_PTR(-ENOENT);+ri=container_of(*cur,structkretprobe_instance,llist);+}while(ri->fp!=fp);++returnret;+}+NOKPROBE_SYMBOL(kretprobe_next_ret_addr);+void__weakarch_kretprobe_fixup_return(structpt_regs*regs,kprobe_opcode_t*correct_ret_addr){
From: "Russell King (Oracle)" <linux@armlinux.org.uk> Date: 2021-10-16 21:16:13
On Fri, Oct 15, 2021 at 09:51:56PM +0900, Masami Hiramatsu wrote:
quoted hunk
Currently kretprobe on ARM just fills r0-r11 of pt_regs, but
that is not enough for the stacktrace. Moreover, from the user
kretprobe handler, stacktrace needs a frame pointer on the
__kretprobe_trampoline.
This adds a frame pointer on __kretprobe_trampoline for both gcc
and clang case. Those have different frame pointer so we need
different but similar stack on pt_regs.
Gcc makes the frame pointer (fp) to point the 'pc' address of
the {fp, ip (=sp), lr, pc}, this means {r11, r13, r14, r15}.
Thus if we save the r11 (fp) on pt_regs->r12, we can make this
set on the end of pt_regs.
On the other hand, Clang makes the frame pointer to point the
'fp' address of {fp, lr} on stack. Since the next to the
pt_regs->lr is pt_regs->sp, I reused the pair of pt_regs->fp
and pt_regs->ip.
So this stores the 'lr' on pt_regs->ip and make the fp to point
pt_regs->fp.
For both cases, saves __kretprobe_trampoline address to
pt_regs->lr, so that the stack tracer can identify this frame
pointer has been made by the __kretprobe_trampoline.
Note that if the CONFIG_FRAME_POINTER is not set, this keeps
fp as is.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
---
arch/arm/probes/kprobes/core.c | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
I think you really do not want to do that.
From DDI0406C:
"ARM deprecates the use of instructions with the base register in the
list and ! specified. If the base register is not the lowest-numbered
register in the list, such an instruction stores an UNKNOWN value for
the base register."
However, it doesn't say what value is stored if the base register is
the lowest-numbered register in the list. The pseudocode given shows
that it is the original value. However, DDI0100E:
"Operand restrictions
If <Rn> is specified as <registers> and base register writeback is
specified:
• If <Rn> is the lowest-numbered register specified in
<register_list>, the original value of <Rn> is stored.
• Otherwise, the stored value of <Rn> is UNPREDICTABLE."
So I guess it might be okay... but it seems a bit dodgy to rely on
this behaviour.
+#ifdef CONFIG_FRAME_POINTER
+ /* __kretprobe_trampoline makes a framepointer on pt_regs. */
+#ifdef CONFIG_CC_IS_CLANG
+ /* In clang case, pt_regs->ip = lr. */
+ "stmdb sp!, {lr} \n\t"
"stmdb sp!, {r0 - r11} \n\t"
This can be simplified to:
"stmdb sp!, {r0 - r11, lr} \n\t"
Also, note the value we store for "fp" is __kretprobe_trampoline.
On Sat, 16 Oct 2021 22:15:57 +0100
"Russell King (Oracle)" [off-list ref] wrote:
On Fri, Oct 15, 2021 at 09:51:56PM +0900, Masami Hiramatsu wrote:
quoted
Currently kretprobe on ARM just fills r0-r11 of pt_regs, but
that is not enough for the stacktrace. Moreover, from the user
kretprobe handler, stacktrace needs a frame pointer on the
__kretprobe_trampoline.
This adds a frame pointer on __kretprobe_trampoline for both gcc
and clang case. Those have different frame pointer so we need
different but similar stack on pt_regs.
Gcc makes the frame pointer (fp) to point the 'pc' address of
the {fp, ip (=sp), lr, pc}, this means {r11, r13, r14, r15}.
Thus if we save the r11 (fp) on pt_regs->r12, we can make this
set on the end of pt_regs.
On the other hand, Clang makes the frame pointer to point the
'fp' address of {fp, lr} on stack. Since the next to the
pt_regs->lr is pt_regs->sp, I reused the pair of pt_regs->fp
and pt_regs->ip.
So this stores the 'lr' on pt_regs->ip and make the fp to point
pt_regs->fp.
For both cases, saves __kretprobe_trampoline address to
pt_regs->lr, so that the stack tracer can identify this frame
pointer has been made by the __kretprobe_trampoline.
Note that if the CONFIG_FRAME_POINTER is not set, this keeps
fp as is.
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
---
arch/arm/probes/kprobes/core.c | 29 ++++++++++++++++++++++++-----
1 file changed, 24 insertions(+), 5 deletions(-)
Yes, I just wants to save the {sp, lr, pc} to mimic the
framepointer.
From DDI0406C:
"ARM deprecates the use of instructions with the base register in the
list and ! specified. If the base register is not the lowest-numbered
register in the list, such an instruction stores an UNKNOWN value for
the base register."
However, it doesn't say what value is stored if the base register is
the lowest-numbered register in the list. The pseudocode given shows
that it is the original value. However, DDI0100E:
"Operand restrictions
If <Rn> is specified as <registers> and base register writeback is
specified:
• If <Rn> is the lowest-numbered register specified in
<register_list>, the original value of <Rn> is stored.
• Otherwise, the stored value of <Rn> is UNPREDICTABLE."
So I guess it might be okay... but it seems a bit dodgy to rely on
this behaviour.
Oh, OK. I just tested it on qemu-arm so maybe it was wrong.
quoted
+#ifdef CONFIG_FRAME_POINTER
+ /* __kretprobe_trampoline makes a framepointer on pt_regs. */
+#ifdef CONFIG_CC_IS_CLANG
+ /* In clang case, pt_regs->ip = lr. */
+ "stmdb sp!, {lr} \n\t"
"stmdb sp!, {r0 - r11} \n\t"
This can be simplified to:
"stmdb sp!, {r0 - r11, lr} \n\t"
Also, note the value we store for "fp" is __kretprobe_trampoline.
Oh, I thought 'r11' is 'fp' from arch/arm/include/uapi/asm/ptrace.h
...
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
...
Is that fp? or ip?