uprobes/x86: emulate push insns for uprobe on x86
Subsystems (4)
PATCH 1/1 uprobes/x86: emulate push insns for uprobe on x86
Uprobe is a tracing mechanism for userspace programs. Typical uprobe will incur overhead of two traps. First trap is caused by replaced trap insn, and the second trap is to execute the original displaced insn in user space.
To reduce the overhead, kernel provides hooks for architectures to emulate the original insn and skip the second trap. In x86, emulation
is done for certain branch insns.
This patch extends the emulation to "push <reg>" insns. These insns are typical in the beginning of the function. For example, bcc
in https://github.com/iovisor/bcc repo provides tools to measure funclantency, detect memleak, etc. The tools will place uprobes in the beginning of function and possibly uretprobes at the end of function. This patch is able to reduce the trap overhead for uprobe from 2 to 1.
Without this patch, uretprobe will typically incur three traps. With this patch, if the function starts with "push" insn, the number of traps can be reduced from 3 to 2.
An experiment was conducted on two local VMs,
fedora 26 64-bit VM and 32-bit VM, both 4 processors and 4GB memory, booted with latest x86/urgent (and this patch). The host is MacBook with intel i7 processor.
The test program looks like
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
static void test() __attribute__((noinline));
void test() {}
int main() {
struct timeval start, end; gettimeofday(&start, NULL);
for (int i = 0; i < 1000000; i++) {
test();
}
gettimeofday(&end, NULL);
printf("%ld\n", ((end.tv_sec * 1000000 + end.tv_usec)
- (start.tv_sec * 1000000 + start.tv_usec)));
return 0;}
The program is compiled without optimization, and the first insn for function "test" is "push %rbp". The host is relatively idle.
Before the test run, the uprobe is inserted as below for uprobe:
echo 'p <binary>:<test_func_offset>' > /sys/kernel/debug/tracing/uprobe_events echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable
and for uretprobe:
echo 'r <binary>:<test_func_offset>' > /sys/kernel/debug/tracing/uprobe_events echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable
Unit: microsecond(usec) per loop iteration
x86_64 W/ this patch W/O this patch uprobe 1.55 3.1 uretprobe 2.0 3.6
x86_32 W/ this patch W/O this patch uprobe 1.41 3.5 uretprobe 1.75 4.0
You can see that this patch significantly reduced the overhead, 50% for uprobe and 44% for uretprobe on x86_64, and even more on x86_32.
--- arch/x86/include/asm/uprobes.h | 10 ++++ arch/x86/kernel/uprobes.c | 115 +++++++++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 16 deletions(-)
Changelog:
. Make commit subject more appropriate
arch/x86/include/asm/uprobes.h +10 -0
@@ -33,6 +33,11 @@ typedef u8 uprobe_opcode_t;#define UPROBE_SWBP_INSN 0xcc#define UPROBE_SWBP_INSN_SIZE 1 +enum uprobe_insn_t { +UPROBE_BRANCH_INSN =0 , +UPROBE_PUSH_INSN =1 , +}; +struct uprobe_xol_ops ;struct arch_uprobe {
@@ -42,6 +47,7 @@ struct arch_uprobe { };const struct uprobe_xol_ops *ops; +enum uprobe_insn_t insn_class; union { struct {
@@ -53,6 +59,10 @@ struct arch_uprobe {u8 fixups;u8 ilen; } defparam; +struct { +u8 rex_prefix; +u8 opc1; + } push; }; };
arch/x86/kernel/uprobes.c +99 -16
161 lines not on this page · raw message
Signature · 1 line
2.9.5
Oleg Nesterov
maintainerQuoted 10 lines · Yonghong Song
This patch extends the emulation to "push <reg>" insns. These insns are typical in the beginning of the function. For example, bcc
in https://github.com/iovisor/bcc repo provides tools to measure funclantency, detect memleak, etc. The tools will place uprobes in the beginning of function and possibly uretprobes at the end of function. This patch is able to reduce the trap overhead for uprobe from 2 to 1.
OK. but to be honest I do not like the implementation, please see below.
arch/x86/include/asm/uprobes.h struct arch_uprobe { · line 50
Quoted 13 lines
arch/x86/include/asm/uprobes.h +6
-0+enum uprobe_insn_t { +UPROBE_BRANCH_INSN =0 , +UPROBE_PUSH_INSN =1 , +}; +struct uprobe_xol_ops ;struct arch_uprobe {@@ -42,6 +47,7 @@ struct arch_uprobe { };const struct uprobe_xol_ops *ops; +enum uprobe_insn_t insn_class;
Why?
I'd suggest to leave branch_xol_ops alone and add the new push_xol_ops{}, the code will look much simpler.
The only thing they can share is branch_post_xol_op() which is just
regs->sp += sizeof_long(); return -ERESTART;
I think a bit of code duplication would be fine in this case.
And. Do you really need ->post_xol() method to emulate "push"? Why we can't simply execute it out-of-line if copy_to_user() fails?
branch_post_xol_op() is needed because we can't execute "call" out-of-line, we need to restart and try again if copy_to_user() fails, but I don not understand why it is needed to emulate "push".
Oleg.
Oleg Nesterov
maintainerQuoted 7 lines · Oleg Nesterov
And. Do you really need ->post_xol() method to emulate "push"? Why we can't simply execute it out-of-line if
copy_to_user()fails?
branch_post_xol_op()is needed because we can't execute "call" out-of-line, we need to restart and try again ifcopy_to_user()fails, but I don not understand why it is needed to emulate "push".
If I wasn't clear, please see the comment in branch_clear_offset().
Oleg.
Quoted 33 lines · Oleg Nesterov
Quoted 10 lines · Yonghong Song
This patch extends the emulation to "push <reg>" insns. These insns are typical in the beginning of the function. For example, bcc
in https://github.com/iovisor/bcc repo provides tools to measure funclantency, detect memleak, etc. The tools will place uprobes in the beginning of function and possibly uretprobes at the end of function. This patch is able to reduce the trap overhead for uprobe from 2 to 1.OK. but to be honest I do not like the implementation, please see below.
arch/x86/include/asm/uprobes.h struct arch_uprobe { · line 50
Quoted 13 lines
arch/x86/include/asm/uprobes.h +6
-0+enum uprobe_insn_t { +UPROBE_BRANCH_INSN =0 , +UPROBE_PUSH_INSN =1 , +}; +struct uprobe_xol_ops ;struct arch_uprobe {@@ -42,6 +47,7 @@ struct arch_uprobe { };const struct uprobe_xol_ops *ops; +enum uprobe_insn_t insn_class;Why?
I'd suggest to leave
branch_xol_opsalone and add the new push_xol_ops{}, the code will look much simpler.
Quoted 6 lines
The only thing they can share is
branch_post_xol_op()which is justregs->sp += sizeof_long(); return -ERESTART;I think a bit of code duplication would be fine in this case.
Just prototyped. Agreed, having seperate uprobe_xol_ops for "push" emulation is clean and better.
Quoted 3 lines
And. Do you really need ->post_xol() method to emulate "push"? Why we can't simply execute it out-of-line if
copy_to_user()fails?
Thanks for pointing it out. Agreed, we do not really need post_xol for "push". xol execution is just fine.
Will address your other comments as well in the next revision.
Quoted 7 lines
branch_post_xol_op()is needed because we can't execute "call" out-of-line, we need to restart and try again ifcopy_to_user()fails, but I don not understand why it is needed to emulate "push".Oleg.
Oleg Nesterov
maintainerarch/x86/kernel/uprobes.c static bool check_jmp_cond(struct arch_uprobe *auprobe, struct pt_regs *regs) · line 703
Quoted 11 lines · Yonghong Song
arch/x86/kernel/uprobes.c +10
-0+if (insn_class ==UPROBE_PUSH_INSN ) { + src_ptr =get_push_reg_ptr (auprobe, regs); + reg_width =sizeof_long (); + sp = regs->sp; +if (copy_to_user ((void __user *)(sp - reg_width), src_ptr, reg_width)) +return false; + + regs->sp = sp - reg_width; + regs->ip +=1 + (auprobe->push.rex_prefix !=0 ); +return true;
Another nit... You can rename push_ret_address() and use it here
src_ptr = ...; if (push_ret_address(regs, *src_ptr)) return false; regs->ip += ...; return true;
and I think get_push_reg_ptr() should just return "unsigned long", not the pointer.
And again, please make a separate method for this code. Let me repeat, the main reason for branch_xol_ops/etc is that we simply can not execute these insns out-of-line, we have to emulate them. "push" differs, the only reason why we may want to emulate it is optimization.
Oleg.