]> Git Repo - linux.git/commitdiff
arm64: insn: Simulate nop instruction for better uprobe performance
authorLiao Chang <[email protected]>
Mon, 9 Sep 2024 07:11:14 +0000 (07:11 +0000)
committerCatalin Marinas <[email protected]>
Tue, 15 Oct 2024 18:43:26 +0000 (19:43 +0100)
v2->v1:
1. Remove the simuation of STP and the related bits.
2. Use arm64_skip_faulting_instruction for single-stepping or FEAT_BTI
   scenario.

As Andrii pointed out, the uprobe/uretprobe selftest bench run into a
counterintuitive result that nop and push variants are much slower than
ret variant [0]. The root cause lies in the arch_probe_analyse_insn(),
which excludes 'nop' and 'stp' from the emulatable instructions list.
This force the kernel returns to userspace and execute them out-of-line,
then trapping back to kernel for running uprobe callback functions. This
leads to a significant performance overhead compared to 'ret' variant,
which is already emulated.

Typicall uprobe is installed on 'nop' for USDT and on function entry
which starts with the instrucion 'stp x29, x30, [sp, #imm]!' to push lr
and fp into stack regardless kernel or userspace binary. In order to
improve the performance of handling uprobe for common usecases. This
patch supports the emulation of Arm64 equvialents instructions of 'nop'
and 'push'. The benchmark results below indicates the performance gain
of emulation is obvious.

On Kunpeng916 (Hi1616), 4 NUMA nodes, 64 Arm64 [email protected].

xol (1 cpus)
------------
uprobe-nop:  0.916 ± 0.001M/s (0.916M/prod)
uprobe-push: 0.908 ± 0.001M/s (0.908M/prod)
uprobe-ret:  1.855 ± 0.000M/s (1.855M/prod)
uretprobe-nop:  0.640 ± 0.000M/s (0.640M/prod)
uretprobe-push: 0.633 ± 0.001M/s (0.633M/prod)
uretprobe-ret:  0.978 ± 0.003M/s (0.978M/prod)

emulation (1 cpus)
-------------------
uprobe-nop:  1.862 ± 0.002M/s  (1.862M/prod)
uprobe-push: 1.743 ± 0.006M/s  (1.743M/prod)
uprobe-ret:  1.840 ± 0.001M/s  (1.840M/prod)
uretprobe-nop:  0.964 ± 0.004M/s  (0.964M/prod)
uretprobe-push: 0.936 ± 0.004M/s  (0.936M/prod)
uretprobe-ret:  0.940 ± 0.001M/s  (0.940M/prod)

As shown above, the performance gap between 'nop/push' and 'ret'
variants has been significantly reduced. Due to the emulation of 'push'
instruction needs to access userspace memory, it spent more cycles than
the other.

As Mark suggested [1], it is painful to emulate the correct atomicity
and ordering properties of STP, especially when it interacts with MTE,
POE, etc. So this patch just focus on the simuation of 'nop'. The
simluation of STP and related changes will be addressed in a separate
patch.

[0] https://lore.kernel.org/all/CAEf4BzaO4eG6hr2hzXYpn+7Uer4chS0R99zLn02ezZ5YruVuQw@mail.gmail.com/
[1] https://lore.kernel.org/all/Zr3RN4zxF5XPgjEB@J2N7QTR9R3/

CC: Andrii Nakryiko <[email protected]>
CC: Mark Rutland <[email protected]>
Signed-off-by: Liao Chang <[email protected]>
Acked-by: Mark Rutland <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
[[email protected]: small tweaks following MarkR's comments]
Signed-off-by: Catalin Marinas <[email protected]>
arch/arm64/include/asm/insn.h
arch/arm64/kernel/probes/decode-insn.c
arch/arm64/kernel/probes/simulate-insn.c
arch/arm64/kernel/probes/simulate-insn.h

index 8c0a36f72d6fcd8947229247dca4a8178b1fa783..89bc18989b90a69e6c897d475f6f70fa8a54a44b 100644 (file)
@@ -575,6 +575,11 @@ static __always_inline u32 aarch64_insn_gen_nop(void)
        return aarch64_insn_gen_hint(AARCH64_INSN_HINT_NOP);
 }
 
+static __always_inline bool aarch64_insn_is_nop(u32 insn)
+{
+       return insn == aarch64_insn_gen_nop();
+}
+
 u32 aarch64_insn_gen_branch_reg(enum aarch64_insn_register reg,
                                enum aarch64_insn_branch_type type);
 u32 aarch64_insn_gen_load_store_reg(enum aarch64_insn_register reg,
index 41b100bcb041d2991af11f07f5abb5997dc0e107..e05249f57075458b207c4e6ec4cc8e86b67a17a0 100644 (file)
@@ -75,6 +75,15 @@ static bool __kprobes aarch64_insn_is_steppable(u32 insn)
 enum probe_insn __kprobes
 arm_probe_decode_insn(u32 insn, struct arch_probe_insn *api)
 {
+       /*
+        * While 'nop' instruction can execute in the out-of-line slot,
+        * simulating them in breakpoint handling offers better performance.
+        */
+       if (aarch64_insn_is_nop(insn)) {
+               api->handler = simulate_nop;
+               return INSN_GOOD_NO_SLOT;
+       }
+
        /*
         * Instructions reading or modifying the PC won't work from the XOL
         * slot.
index b65334ab79d2b0e9190f8349546ebe497c5e5b91..4c6d2d712fbd3c4ed5c7cc38eb6be0348d4e1034 100644 (file)
@@ -196,3 +196,9 @@ simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs)
 
        instruction_pointer_set(regs, instruction_pointer(regs) + 4);
 }
+
+void __kprobes
+simulate_nop(u32 opcode, long addr, struct pt_regs *regs)
+{
+       arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);
+}
index e065dc92218e8314fed265a0905be6bd87812497..efb2803ec943d6e3443e2b311edbed10c4acecf0 100644 (file)
@@ -16,5 +16,6 @@ void simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs);
 void simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs);
 void simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs);
 void simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs);
+void simulate_nop(u32 opcode, long addr, struct pt_regs *regs);
 
 #endif /* _ARM_KERNEL_KPROBES_SIMULATE_INSN_H */
This page took 0.066065 seconds and 4 git commands to generate.