Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bpf/optimized usdt ci #8161

Closed
Closed
2 changes: 1 addition & 1 deletion arch/arm/probes/uprobes/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ int set_swbp(struct arch_uprobe *auprobe, struct mm_struct *mm,
unsigned long vaddr)
{
return uprobe_write_opcode(auprobe, mm, vaddr,
__opcode_to_mem_arm(auprobe->bpinsn));
__opcode_to_mem_arm(auprobe->bpinsn), NULL);
}

bool arch_uprobe_ignore(struct arch_uprobe *auprobe, struct pt_regs *regs)
Expand Down
1 change: 1 addition & 0 deletions arch/x86/entry/syscalls/syscall_64.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
333 common io_pgetevents sys_io_pgetevents
334 common rseq sys_rseq
335 common uretprobe sys_uretprobe
336 common uprobe sys_uprobe
# don't use numbers 387 through 423, add new calls after the last
# 'common' entry
424 common pidfd_send_signal sys_pidfd_send_signal
Expand Down
7 changes: 7 additions & 0 deletions arch/x86/include/asm/uprobes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ typedef u8 uprobe_opcode_t;
#define UPROBE_SWBP_INSN 0xcc
#define UPROBE_SWBP_INSN_SIZE 1

enum {
ARCH_UPROBE_FLAG_CAN_OPTIMIZE = 0,
ARCH_UPROBE_FLAG_OPTIMIZED = 1,
};

struct uprobe_xol_ops;

struct arch_uprobe {
Expand All @@ -45,6 +50,8 @@ struct arch_uprobe {
u8 ilen;
} push;
};

unsigned long flags;
};

struct arch_uprobe_task {
Expand Down
216 changes: 215 additions & 1 deletion arch/x86/kernel/uprobes.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <asm/processor.h>
#include <asm/insn.h>
#include <asm/mmu_context.h>
#include <asm/nops.h>

/* Post-execution fixups. */

Expand Down Expand Up @@ -338,7 +339,7 @@ extern u8 uretprobe_trampoline_entry[];
extern u8 uretprobe_trampoline_end[];
extern u8 uretprobe_syscall_check[];

void *arch_uprobe_trampoline(unsigned long *psize)
void *arch_uretprobe_trampoline(unsigned long *psize)
{
static uprobe_opcode_t insn = UPROBE_SWBP_INSN;
struct pt_regs *regs = task_pt_regs(current);
Expand Down Expand Up @@ -425,6 +426,77 @@ SYSCALL_DEFINE0(uretprobe)
return -1;
}

SYSCALL_DEFINE0(uprobe)
{
struct pt_regs *regs = task_pt_regs(current);
unsigned long bp_vaddr;
int err;

err = copy_from_user(&bp_vaddr, (void __user *)regs->sp + 3*8, sizeof(bp_vaddr));
if (err) {
force_sig(SIGILL);
return -1;
}

handle_syscall_uprobe(regs, bp_vaddr - 5);
return 0;
}

asm (
".pushsection .rodata\n"
".global uprobe_trampoline_entry\n"
"uprobe_trampoline_entry:\n"
"push %rcx\n"
"push %r11\n"
"push %rax\n"
"movq $" __stringify(__NR_uprobe) ", %rax\n"
"syscall\n"
"pop %rax\n"
"pop %r11\n"
"pop %rcx\n"
"ret\n"
".global uprobe_trampoline_end\n"
"uprobe_trampoline_end:\n"
".popsection\n"
);

extern __visible u8 uprobe_trampoline_entry[];
extern __visible u8 uprobe_trampoline_end[];

static int tramp_mremap(const struct vm_special_mapping *sm, struct vm_area_struct *new_vma)
{
return -EPERM;
}

static struct vm_special_mapping tramp_mapping = {
.name = "[uprobes-trampoline]",
.mremap = tramp_mremap,
};

const struct vm_special_mapping *arch_uprobe_trampoline_mapping(void)
{
struct pt_regs *regs = task_pt_regs(current);

return user_64bit_mode(regs) ? &tramp_mapping : NULL;
}

static int __init arch_uprobes_init(void)
{
unsigned long size = uprobe_trampoline_end - uprobe_trampoline_entry;
static struct page *pages[2];
struct page *page;

page = alloc_page(GFP_HIGHUSER);
if (!page)
return -ENOMEM;
pages[0] = page;
tramp_mapping.pages = (struct page **) &pages;
arch_uprobe_copy_ixol(page, 0, uprobe_trampoline_entry, size);
return 0;
}

late_initcall(arch_uprobes_init);

/*
* If arch_uprobe->insn doesn't use rip-relative addressing, return
* immediately. Otherwise, rewrite the instruction so that it accesses
Expand Down Expand Up @@ -829,6 +901,33 @@ static const struct uprobe_xol_ops push_xol_ops = {
.emulate = push_emulate_op,
};

static int is_nop5_insn(uprobe_opcode_t *insn)
{
return !memcmp(insn, x86_nops[5], 5);
}

static int is_call_insn(uprobe_opcode_t *insn)
{
return *insn == CALL_INSN_OPCODE;
}

static void relative_insn(void *dest, void *from, void *to, u8 op)
{
struct __arch_relative_insn {
u8 op;
s32 raddr;
} __packed *insn;

insn = (struct __arch_relative_insn *)dest;
insn->raddr = (s32)((long)(to) - ((long)(from) + 5));
insn->op = op;
}

static void relative_call(void *dest, void *from, void *to)
{
relative_insn(dest, from, to, CALL_INSN_OPCODE);
}

/* Returns -ENOSYS if branch_xol_ops doesn't handle this insn */
static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
{
Expand All @@ -848,6 +947,10 @@ static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
break;

case 0x0f:
if (is_nop5_insn((uprobe_opcode_t *) &auprobe->insn)) {
set_bit(ARCH_UPROBE_FLAG_CAN_OPTIMIZE, &auprobe->flags);
goto setup;
}
if (insn->opcode.nbytes != 2)
return -ENOSYS;
/*
Expand Down Expand Up @@ -1219,3 +1322,114 @@ bool arch_uretprobe_is_alive(struct return_instance *ret, enum rp_check ctx,
else
return regs->sp <= ret->stack;
}

int arch_uprobe_verify_opcode(struct page *page, unsigned long vaddr,
uprobe_opcode_t *new_opcode, void *opt)
{
if (opt) {
bool is_call, is_swbp, is_nop5;
uprobe_opcode_t old_opcode[5];

uprobe_copy_from_page(page, vaddr, (uprobe_opcode_t *) &old_opcode, 5);
is_call = is_call_insn((uprobe_opcode_t *) &old_opcode);
is_swbp = is_swbp_insn((uprobe_opcode_t *) &old_opcode);
is_nop5 = is_nop5_insn((uprobe_opcode_t *) &old_opcode);

if (is_call_insn(new_opcode)) {
/*
* Both is_swbp and is_nop5 are valid starting states for installing
* optimized uprobe call, because we could have multiple threads
* attaching/detaching consumers on single uprobe.
*/
if (is_swbp || is_nop5)
return 1;
if (is_call && !memcmp(new_opcode, &old_opcode, 5))
return 0;
} else {
/*
* We could detach before uprobe gets optimized, hence the is_swbp
* is valid state in here.
*/
if (is_call || is_swbp)
return 1;
if (is_nop5)
return 0;
}

WARN_ONCE(1, "old: is_call %d is_swbp %d is_nop5 %d, new: is_call %d\n",
is_call, is_swbp, is_nop5, is_call_insn(new_opcode));
return -1;
}

return uprobe_verify_opcode(page, vaddr, new_opcode);
}

bool arch_uprobe_is_register(uprobe_opcode_t *insn, int len, void *data)
{
return data ? len == 5 && is_call_insn(insn) : is_swbp_insn(insn);
}

static void __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct *mm,
unsigned long vaddr)
{
struct uprobe_trampoline *tramp = NULL;
char call[5];

/* We can't do cross page atomic writes yet. */
if (PAGE_SIZE - (vaddr & ~PAGE_MASK) < 5)
goto fail;

tramp = uprobe_trampoline_get(vaddr);
if (!tramp)
goto fail;

relative_call(call, (void *) vaddr, (void *) tramp->vaddr);
if (uprobe_write_opcode(auprobe, mm, vaddr, call, 5, (void *) 1))
goto fail;

set_bit(ARCH_UPROBE_FLAG_OPTIMIZED, &auprobe->flags);
return;

fail:
/* Once we fail we never try again. */
clear_bit(ARCH_UPROBE_FLAG_CAN_OPTIMIZE, &auprobe->flags);
uprobe_trampoline_put(tramp);
}

static bool should_optimize(struct arch_uprobe *auprobe)
{
if (!test_bit(ARCH_UPROBE_FLAG_CAN_OPTIMIZE, &auprobe->flags))
return false;
if (test_bit(ARCH_UPROBE_FLAG_OPTIMIZED, &auprobe->flags))
return false;
return true;
}

void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
{
struct mm_struct *mm = current->mm;

if (!should_optimize(auprobe))
return;

mmap_write_lock(mm);
if (should_optimize(auprobe))
__arch_uprobe_optimize(auprobe, mm, vaddr);
mmap_write_unlock(mm);
}

int set_orig_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr)
{
uprobe_opcode_t *insn = (uprobe_opcode_t *) auprobe->insn;

if (test_bit(ARCH_UPROBE_FLAG_OPTIMIZED, &auprobe->flags))
return uprobe_write_opcode(auprobe, mm, vaddr, insn, 5, (void *) 1);

return uprobe_write_opcode(auprobe, mm, vaddr, insn, UPROBE_SWBP_INSN_SIZE, NULL);
}

bool arch_uprobe_is_callable(unsigned long vtramp, unsigned long vaddr)
{
long delta = (long)(vaddr + 5 - vtramp);
return delta >= INT_MIN && delta <= INT_MAX;
}
2 changes: 2 additions & 0 deletions include/linux/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,8 @@ asmlinkage long sys_ioperm(unsigned long from, unsigned long num, int on);

asmlinkage long sys_uretprobe(void);

asmlinkage long sys_uprobe(void);

/* pciconfig: alpha, arm, arm64, ia64, sparc */
asmlinkage long sys_pciconfig_read(unsigned long bus, unsigned long dfn,
unsigned long off, unsigned long len,
Expand Down
24 changes: 22 additions & 2 deletions include/linux/uprobes.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/mutex.h>

struct uprobe;
struct vm_area_struct;
Expand Down Expand Up @@ -172,6 +173,13 @@ struct xol_area;

struct uprobes_state {
struct xol_area *xol_area;
struct hlist_head tramp_head;
};

struct uprobe_trampoline {
struct hlist_node node;
unsigned long vaddr;
atomic64_t ref;
};

extern void __init uprobes_init(void);
Expand All @@ -181,7 +189,8 @@ extern bool is_swbp_insn(uprobe_opcode_t *insn);
extern bool is_trap_insn(uprobe_opcode_t *insn);
extern unsigned long uprobe_get_swbp_addr(struct pt_regs *regs);
extern unsigned long uprobe_get_trap_addr(struct pt_regs *regs);
extern int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr, uprobe_opcode_t);
extern int uprobe_write_opcode(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long vaddr,
uprobe_opcode_t *insn, int nbytes, void *data);
extern struct uprobe *uprobe_register(struct inode *inode, loff_t offset, loff_t ref_ctr_offset, struct uprobe_consumer *uc);
extern int uprobe_apply(struct uprobe *uprobe, struct uprobe_consumer *uc, bool);
extern void uprobe_unregister_nosync(struct uprobe *uprobe, struct uprobe_consumer *uc);
Expand Down Expand Up @@ -211,8 +220,19 @@ extern bool arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs);
extern void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
void *src, unsigned long len);
extern void uprobe_handle_trampoline(struct pt_regs *regs);
extern void *arch_uprobe_trampoline(unsigned long *psize);
extern void *arch_uretprobe_trampoline(unsigned long *psize);
extern unsigned long uprobe_get_trampoline_vaddr(void);
extern void uprobe_copy_from_page(struct page *page, unsigned long vaddr, void *dst, int len);
extern int uprobe_verify_opcode(struct page *page, unsigned long vaddr, uprobe_opcode_t *new_opcode);
extern int arch_uprobe_verify_opcode(struct page *page, unsigned long vaddr,
uprobe_opcode_t *new_opcode, void *data);
extern bool arch_uprobe_is_register(uprobe_opcode_t *insn, int nbytes, void *data);
extern struct uprobe_trampoline *uprobe_trampoline_get(unsigned long vaddr);
extern void uprobe_trampoline_put(struct uprobe_trampoline *area);
extern bool arch_uprobe_is_callable(unsigned long vtramp, unsigned long vaddr);
extern const struct vm_special_mapping *arch_uprobe_trampoline_mapping(void);
extern void handle_syscall_uprobe(struct pt_regs *regs, unsigned long bp_vaddr);
extern void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr);
#else /* !CONFIG_UPROBES */
struct uprobes_state {
};
Expand Down
Loading
Loading