| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2023 Hengqi Chen */ |
| |
| #include <test_progs.h> |
| #include <asm/ptrace.h> |
| #include "test_uprobe.skel.h" |
| |
| static FILE *urand_spawn(int *pid) |
| { |
| FILE *f; |
| |
| /* urandom_read's stdout is wired into f */ |
| f = popen("./urandom_read 1 report-pid", "r"); |
| if (!f) |
| return NULL; |
| |
| if (fscanf(f, "%d", pid) != 1) { |
| pclose(f); |
| errno = EINVAL; |
| return NULL; |
| } |
| |
| return f; |
| } |
| |
| static int urand_trigger(FILE **urand_pipe) |
| { |
| int exit_code; |
| |
| /* pclose() waits for child process to exit and returns their exit code */ |
| exit_code = pclose(*urand_pipe); |
| *urand_pipe = NULL; |
| |
| return exit_code; |
| } |
| |
| static void test_uprobe_attach(void) |
| { |
| LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); |
| struct test_uprobe *skel; |
| FILE *urand_pipe = NULL; |
| int urand_pid = 0, err; |
| |
| skel = test_uprobe__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "skel_open")) |
| return; |
| |
| urand_pipe = urand_spawn(&urand_pid); |
| if (!ASSERT_OK_PTR(urand_pipe, "urand_spawn")) |
| goto cleanup; |
| |
| skel->bss->my_pid = urand_pid; |
| |
| /* Manual attach uprobe to urandlib_api |
| * There are two `urandlib_api` symbols in .dynsym section: |
| * - urandlib_api@LIBURANDOM_READ_1.0.0 |
| * - urandlib_api@@LIBURANDOM_READ_2.0.0 |
| * Both are global bind and would cause a conflict if user |
| * specify the symbol name without a version suffix |
| */ |
| uprobe_opts.func_name = "urandlib_api"; |
| skel->links.test4 = bpf_program__attach_uprobe_opts(skel->progs.test4, |
| urand_pid, |
| "./liburandom_read.so", |
| 0 /* offset */, |
| &uprobe_opts); |
| if (!ASSERT_ERR_PTR(skel->links.test4, "urandlib_api_attach_conflict")) |
| goto cleanup; |
| |
| uprobe_opts.func_name = "urandlib_api@LIBURANDOM_READ_1.0.0"; |
| skel->links.test4 = bpf_program__attach_uprobe_opts(skel->progs.test4, |
| urand_pid, |
| "./liburandom_read.so", |
| 0 /* offset */, |
| &uprobe_opts); |
| if (!ASSERT_OK_PTR(skel->links.test4, "urandlib_api_attach_ok")) |
| goto cleanup; |
| |
| /* Auto attach 3 u[ret]probes to urandlib_api_sameoffset */ |
| err = test_uprobe__attach(skel); |
| if (!ASSERT_OK(err, "skel_attach")) |
| goto cleanup; |
| |
| /* trigger urandom_read */ |
| ASSERT_OK(urand_trigger(&urand_pipe), "urand_exit_code"); |
| |
| ASSERT_EQ(skel->bss->test1_result, 1, "urandlib_api_sameoffset"); |
| ASSERT_EQ(skel->bss->test2_result, 1, "urandlib_api_sameoffset@v1"); |
| ASSERT_EQ(skel->bss->test3_result, 3, "urandlib_api_sameoffset@@v2"); |
| ASSERT_EQ(skel->bss->test4_result, 1, "urandlib_api"); |
| |
| cleanup: |
| if (urand_pipe) |
| pclose(urand_pipe); |
| test_uprobe__destroy(skel); |
| } |
| |
| #ifdef __x86_64__ |
| __naked __maybe_unused unsigned long uprobe_regs_change_trigger(void) |
| { |
| asm volatile ( |
| "ret\n" |
| ); |
| } |
| |
| static __naked void uprobe_regs_change(struct pt_regs *before, struct pt_regs *after) |
| { |
| asm volatile ( |
| "movq %r11, 48(%rdi)\n" |
| "movq %r10, 56(%rdi)\n" |
| "movq %r9, 64(%rdi)\n" |
| "movq %r8, 72(%rdi)\n" |
| "movq %rax, 80(%rdi)\n" |
| "movq %rcx, 88(%rdi)\n" |
| "movq %rdx, 96(%rdi)\n" |
| "movq %rsi, 104(%rdi)\n" |
| "movq %rdi, 112(%rdi)\n" |
| |
| /* save 2nd argument */ |
| "pushq %rsi\n" |
| "call uprobe_regs_change_trigger\n" |
| |
| /* save return value and load 2nd argument pointer to rax */ |
| "pushq %rax\n" |
| "movq 8(%rsp), %rax\n" |
| |
| "movq %r11, 48(%rax)\n" |
| "movq %r10, 56(%rax)\n" |
| "movq %r9, 64(%rax)\n" |
| "movq %r8, 72(%rax)\n" |
| "movq %rcx, 88(%rax)\n" |
| "movq %rdx, 96(%rax)\n" |
| "movq %rsi, 104(%rax)\n" |
| "movq %rdi, 112(%rax)\n" |
| |
| /* restore return value and 2nd argument */ |
| "pop %rax\n" |
| "pop %rsi\n" |
| |
| "movq %rax, 80(%rsi)\n" |
| "ret\n" |
| ); |
| } |
| |
| static void regs_common(void) |
| { |
| struct pt_regs before = {}, after = {}, expected = { |
| .rax = 0xc0ffe, |
| .rcx = 0xbad, |
| .rdx = 0xdead, |
| .r8 = 0x8, |
| .r9 = 0x9, |
| .r10 = 0x10, |
| .r11 = 0x11, |
| .rdi = 0x12, |
| .rsi = 0x13, |
| }; |
| LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); |
| struct test_uprobe *skel; |
| |
| skel = test_uprobe__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "skel_open")) |
| return; |
| |
| skel->bss->my_pid = getpid(); |
| skel->bss->regs = expected; |
| |
| uprobe_opts.func_name = "uprobe_regs_change_trigger"; |
| skel->links.test_regs_change = bpf_program__attach_uprobe_opts(skel->progs.test_regs_change, |
| -1, |
| "/proc/self/exe", |
| 0 /* offset */, |
| &uprobe_opts); |
| if (!ASSERT_OK_PTR(skel->links.test_regs_change, "bpf_program__attach_uprobe_opts")) |
| goto cleanup; |
| |
| uprobe_regs_change(&before, &after); |
| |
| ASSERT_EQ(after.rax, expected.rax, "ax"); |
| ASSERT_EQ(after.rcx, expected.rcx, "cx"); |
| ASSERT_EQ(after.rdx, expected.rdx, "dx"); |
| ASSERT_EQ(after.r8, expected.r8, "r8"); |
| ASSERT_EQ(after.r9, expected.r9, "r9"); |
| ASSERT_EQ(after.r10, expected.r10, "r10"); |
| ASSERT_EQ(after.r11, expected.r11, "r11"); |
| ASSERT_EQ(after.rdi, expected.rdi, "rdi"); |
| ASSERT_EQ(after.rsi, expected.rsi, "rsi"); |
| |
| cleanup: |
| test_uprobe__destroy(skel); |
| } |
| |
| static noinline unsigned long uprobe_regs_change_ip_1(void) |
| { |
| return 0xc0ffee; |
| } |
| |
| static noinline unsigned long uprobe_regs_change_ip_2(void) |
| { |
| return 0xdeadbeef; |
| } |
| |
| static void regs_ip(void) |
| { |
| LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); |
| struct test_uprobe *skel; |
| unsigned long ret; |
| |
| skel = test_uprobe__open_and_load(); |
| if (!ASSERT_OK_PTR(skel, "skel_open")) |
| return; |
| |
| skel->bss->my_pid = getpid(); |
| skel->bss->ip = (unsigned long) uprobe_regs_change_ip_2; |
| |
| uprobe_opts.func_name = "uprobe_regs_change_ip_1"; |
| skel->links.test_regs_change_ip = bpf_program__attach_uprobe_opts( |
| skel->progs.test_regs_change_ip, |
| -1, |
| "/proc/self/exe", |
| 0 /* offset */, |
| &uprobe_opts); |
| if (!ASSERT_OK_PTR(skel->links.test_regs_change_ip, "bpf_program__attach_uprobe_opts")) |
| goto cleanup; |
| |
| ret = uprobe_regs_change_ip_1(); |
| ASSERT_EQ(ret, 0xdeadbeef, "ret"); |
| |
| cleanup: |
| test_uprobe__destroy(skel); |
| } |
| |
| static void test_uprobe_regs_change(void) |
| { |
| if (test__start_subtest("regs_change_common")) |
| regs_common(); |
| if (test__start_subtest("regs_change_ip")) |
| regs_ip(); |
| } |
| #else |
| static void test_uprobe_regs_change(void) { } |
| #endif |
| |
| void test_uprobe(void) |
| { |
| if (test__start_subtest("attach")) |
| test_uprobe_attach(); |
| test_uprobe_regs_change(); |
| } |