From 41d0c86b2ada3b91ece2a58253e6d1b1c6b101ae Mon Sep 17 00:00:00 2001 From: gbelinassi Date: Wed, 24 Jul 2024 13:41:08 -0300 Subject: [PATCH] Port libpulp to Powerpc64le This commit adds a new port to libpulp: Powerpc64le. Signed-off-by: gbelinassi --- configure.ac | 43 ++++- include/arch/powerpc64le/arch_common.h | 34 ++++ include/arch/x86_64/arch_common.h | 3 - lib/Makefile.am | 2 +- lib/arch/powerpc64le/patch.c | 224 +++++++++++++++++++++++++ lib/arch/powerpc64le/ulp_interface.S | 131 +++++++++++++++ lib/interpose.c | 1 + tests/Makefile.am | 2 +- tests/asunsafe_conversion.py | 2 +- tests/libpagecross_padding.S | 1 - tests/libtls.c | 6 + tests/libtls_livepatch1.c | 3 + tests/process_access.c | 12 +- tests/redzone.c | 32 +++- tests/tls.c | 5 +- tools/arch/powerpc64le/post-arch.c | 75 +++++++++ 16 files changed, 549 insertions(+), 27 deletions(-) create mode 100644 include/arch/powerpc64le/arch_common.h create mode 100644 lib/arch/powerpc64le/patch.c create mode 100644 lib/arch/powerpc64le/ulp_interface.S create mode 100644 tools/arch/powerpc64le/post-arch.c diff --git a/configure.ac b/configure.ac index 0754e66d8..87a306764 100644 --- a/configure.ac +++ b/configure.ac @@ -139,7 +139,7 @@ AC_SUBST([AM_CCASFLAGS], ["-Wa,--fatal-warnings"]) # Use the glibc versions installed on path AC_ARG_WITH([glibc], AS_HELP_STRING([--with-glibc=GLIBC_PATH],[Use the glibc installed on GLIBC_PATH, where the .so file is present]), -[AC_SUBST([AM_LDFLAGS], ["-Wl,--dynamic-linker=$with_glibc/ld-linux-x86-64.so.2 -Wl,--rpath=$with_glibc/"])], +[AC_SUBST([AM_LDFLAGS], ["-Wl,--dynamic-linker=$with_glibc/ld-linux-${target_cpu//_/-}.so.2 -Wl,--rpath=$with_glibc/"])], []) # Checking the call stack of all threads enables libpulp to only apply a live @@ -223,14 +223,51 @@ AC_SUBST([PAGE_SIZE], [$($GETCONF PAGE_SIZE)])) # before the entry point, and the remaining nops after it. At running time, # whenever a live patch is applied, libpulp replaces the remaining nops with # instructions that redirect execution to the universe handling routines. -AC_SUBST([ULP_NOPS_LEN], [16]) -AC_SUBST([PRE_NOPS_LEN], [14]) +_NOPS_LEN=0 +_PRE_NOPS_LEN=0 + +AS_CASE([$host_cpu], + [x86_64], + [ + _NOPS_LEN=16 + _PRE_NOPS_LEN=14 + ], + [powerpc64le], + [ + _NOPS_LEN=19 + _PRE_NOPS_LEN=18 + ] +) + +AC_SUBST([ULP_NOPS_LEN], [$_NOPS_LEN]) +AC_SUBST([PRE_NOPS_LEN], [$_PRE_NOPS_LEN]) AC_DEFINE_UNQUOTED([ULP_NOPS_LEN], [$ULP_NOPS_LEN], [Total number of padding nops]) AC_DEFINE_UNQUOTED([PRE_NOPS_LEN], [$PRE_NOPS_LEN], [Padding nops before the entry point of functions]) +_LD_LINUX="" +AC_CHECK_FILE("/usr/lib64/ld-linux-x86-64.so.2", + AC_SUBST([_LD_LINUX], "ld-linux-x86-64.so.2")) +AC_CHECK_FILE("/usr/lib64/ld64.so.2", + AC_SUBST([_LD_LINUX], "ld64.so.2")) + +AC_DEFINE_UNQUOTED([LD_LINUX], ["$_LD_LINUX"], [Path to the ld-linux loader] ) + +# Check if -fpatchable-function-entry=$ULP_NOPS_LEN,$PRE_NOPS_LEN works +# correctly. +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[extern void g(void); + __attribute__((patchable_function_entry($_NOPS_LEN, $_PRE_NOPS_LEN))) + void f(void) { g(); }]])], + [patchable_works=yes], + [patchable_works=no]) + +AS_IF([test "x$patchable_works" == "xno"], +AC_MSG_ERROR( +[The -fpatchable-functions-entry flag of your C compiler does not work correctly])) + AC_CONFIG_FILES([Makefile include/Makefile lib/Makefile diff --git a/include/arch/powerpc64le/arch_common.h b/include/arch/powerpc64le/arch_common.h new file mode 100644 index 000000000..d1a9949be --- /dev/null +++ b/include/arch/powerpc64le/arch_common.h @@ -0,0 +1,34 @@ +#ifndef _ARCH_PPC64LE_H +#define _ARCH_PPC64LE_H + +/** Offset of TLS pointer. */ +#define TLS_DTV_OFFSET 0x8000 + +/** Struct used to store the registers in memory. */ +typedef struct pt_regs registers_t; + +/** Register in which the function stores the return value. */ +#define FUNCTION_RETURN_REG(reg) ((reg).gpr[3]) + +/** Register which acts as a program counter. */ +#define PROGRAM_COUNTER_REG(reg) ((reg).nip) + +/** Register which acts as top of stack. */ +#define STACK_TOP_REG(reg) ((reg).gpr[1]) + +/** Set the GLOBAL ENTRYPOINT REGISTER, which in power is r12. */ +#define SET_GLOBAL_ENTRYPOINT_REG(reg, val) (reg).gpr[12] = (val) + +/* Program load bias, which can be recovered by running `ld --verbose`. */ +#define EXECUTABLE_START 0x10000000UL + +/* The Red zone. */ +#define RED_ZONE_LEN 512 + +/** + * Number of bytes that the kernel subtracts from the program counter, + * when an ongoing syscall gets interrupted and must be restarted. + */ +#define RESTART_SYSCALL_SIZE 0 + +#endif diff --git a/include/arch/x86_64/arch_common.h b/include/arch/x86_64/arch_common.h index 16cf8baf7..690f5f75c 100644 --- a/include/arch/x86_64/arch_common.h +++ b/include/arch/x86_64/arch_common.h @@ -28,9 +28,6 @@ typedef struct user_regs_struct registers_t; /** The red zone. */ #define RED_ZONE_LEN 128 -/** File name of the dynamic loader. */ -#define LD_LINUX "ld-linux-x86-64.so.2" - /** * Number of bytes that the kernel subtracts from the program counter, * when an ongoing syscall gets interrupted and must be restarted. diff --git a/lib/Makefile.am b/lib/Makefile.am index faf262c47..483717bc2 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -30,7 +30,7 @@ libpulp_la_SOURCES = \ libpulp_la_DEPENDENCIES= libpulp.versions libpulp_la_LDFLAGS = \ -ldl \ - -l:ld-linux-x86-64.so.2 \ + -l:@_LD_LINUX@ \ -Wl,--version-script=$(srcdir)/libpulp.versions \ -Wl,--hash-style=sysv \ # Ubuntu seems to default to gnu, so be clear we ... $(AM_LDFLAGS) # ... want old style hash sections, else DT_HASH is empty. diff --git a/lib/arch/powerpc64le/patch.c b/lib/arch/powerpc64le/patch.c new file mode 100644 index 000000000..7d5a7f9ea --- /dev/null +++ b/lib/arch/powerpc64le/patch.c @@ -0,0 +1,224 @@ +/* + * libpulp - User-space Livepatching Library + * + * Copyright (C) 2017-2023 SUSE Software Solutions GmbH + * + * This file is part of libpulp. + * + * libpulp is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libpulp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libpulp. If not, see . + */ + +#define _GNU_SOURCE + +#include +#include +#include + +#include "config.h" +#include "error.h" +#include "insn_queue_lib.h" +#include "msg_queue.h" +#include "ulp.h" + +/* clang-format off */ + +/** Size of each instructions, in bytes. */ +#define INSN_SIZE 4 + +static unsigned char ulp_prologue[INSN_SIZE * PRE_NOPS_LEN] = { + 0x02, 0x10, 0x40, 0x3c, // lis r2,4098 + 0x00, 0x7f, 0x42, 0x38, // addi r2,r2,32512 + 0x22, 0x11, 0x80, 0x3d, // lis r12,0x1122 + 0xa6, 0x02, 0x08, 0x7c, // mflr r0 + 0x44, 0x33, 0x8c, 0x61, // ori r12,r12,0x3344 + 0xc6, 0x07, 0x8c, 0x79, // sldi r12,r12,32 + 0x66, 0x55, 0x8c, 0x65, // oris r12,r12,0x5566 + 0x10, 0x00, 0x01, 0xf8, // std r0,16(r1) + 0xe1, 0xff, 0x21, 0xf8, // stdu r1,-32(r1) + 0x88, 0x77, 0x8c, 0x61, // ori r12,r12,0x7788 + 0x18, 0x00, 0x41, 0xf8, // std r2,24(r1) + 0xa6, 0x03, 0x89, 0x7d, // mtctr r12 + 0x21, 0x04, 0x80, 0x4e, // bctrl + 0x18, 0x00, 0x41, 0xe8, // ld r2,24(r1) + 0x20, 0x00, 0x21, 0x38, // addi r1,r1,32 + 0x10, 0x00, 0x01, 0xe8, // ld r0,16(r1) + 0xa6, 0x03, 0x08, 0x7c, // mtlr r0 + 0x20, 0x00, 0x80, 0x4e, // blr +}; + +/** The NOP instruction. */ +static const unsigned char gNop[] = { 0x00, 0x00, 0x00, 0x60 }; + +/** Generate a branch (b) instruction according to offset. */ +static uint32_t +generate_branch_to_prologue(int32_t offset) +{ + return (offset & 0x00FFFFFF) | (0x4B << 24); +} + +#define WITH_OFFSET(x) (-(INSN_SIZE * PRE_NOPS_LEN + (offset))) +#define WITHOUT_OFFSET WITH_OFFSET(0) + +/* clang-format on */ + +/** @brief Copy the ulp proglogue layout into the function to be patched's + * prologue + * + * This function copies the new code prologue into the old function prologue + * in order to redirect the execution to the new function. + * + */ +static void +ulp_patch_prologue_layout(void *old_fentry, void *new_fentry, const unsigned char *prologue, int len) +{ + (void) len; + + /* Create a copy of the prologue. */ + unsigned char prolog[INSN_SIZE*PRE_NOPS_LEN]; + _Static_assert(sizeof(prolog) == sizeof(ulp_prologue), + "Prologue sizes do not match"); + memcpy(prolog, prologue, sizeof(prolog)); + + unsigned char new_fentry_bytes[sizeof(void*)]; + memcpy(new_fentry_bytes, &new_fentry, sizeof(new_fentry_bytes)); + + /* Patch the code with the address of the function we want to be redirected. */ + prolog[8] = new_fentry_bytes[6]; + prolog[9] = new_fentry_bytes[7]; + prolog[16] = new_fentry_bytes[4]; + prolog[17] = new_fentry_bytes[5]; + prolog[24] = new_fentry_bytes[2]; + prolog[25] = new_fentry_bytes[3]; + prolog[36] = new_fentry_bytes[0]; + prolog[37] = new_fentry_bytes[1]; + + /* Point to the prologue. */ + char *fentry_prologue = old_fentry - INSN_SIZE * PRE_NOPS_LEN; + insnq_insert_write(fentry_prologue, INSN_SIZE * PRE_NOPS_LEN, prolog); +} + +/** @brief Get the offset of the NOP instruction. + * + * Some function do not have a global entry point prologue, that means + * the NOP instruction is placed at the same address as the calling point. + * We have to figure out which case we are handling. + */ +static int +get_branch_offset(void *fentry) +{ + int valid_offsets[] = { + 0, // NOP located at the calling point. + 8, // func with global entry point, NOP is located 8 bytes after it. + }; + + for (unsigned i = 0; i < ARRAY_LENGTH(valid_offsets); i++) { + int offset = valid_offsets[i]; + void *fpos = (void *) ((char *)fentry + offset); + + /* Generate a branch instruction to the begining of the NOP prologue. */ + uint32_t branch = generate_branch_to_prologue(WITH_OFFSET(offset)); + + /* There are two cases we must check: + - Function not livepatched: have a NOP insn here. + - Function is livepatched: have a B (branch) insn here. */ + if (memcmp(fpos, gNop, sizeof(gNop)) == 0 || + memcmp(fpos, &branch, sizeof(branch)) == 0) { + return offset; + } + } + + /* Not valid. */ + return -INT_MAX; +} + +/** @brief skip the ulp prologue. + * + * When a function gets live patch, the nops at its entry point get replaced + * with a backwards-jump to a small segment of code that redirects execution to + * the new version of the function. However, when all live patches to said + * function are deactivated (because the live patches have been reversed), the + * need for the backwards-jump is gone. + * + * The following function replaces the backwards-jump with nops, thus making + * the target function look like it did at the beginning of execution, i.e. + * without live patches. + * + * @param fentry Address to write the prologue to. + */ +static int +ulp_skip_prologue(void *fentry) +{ + int offset = get_branch_offset(fentry); + if (offset < 0) { + return ENOPATCHABLE; + } + + unsigned char *dst = (unsigned char *)fentry + get_branch_offset(fentry); + insnq_insert_write(dst, sizeof(gNop), gNop); + + return 0; +} + +/** @brief Insert the backwards jump to the NOP prologue. + * + * When a function gets live patch, the nops at its entry point get replaced + * with a backwards-jump to a small segment of code that redirects execution to + * the new version of the function. This function does exactly this. + * + * @param fentry Address to write the prologue to. + */ +static int +ulp_patch_addr_trampoline(void *old_fentry) +{ + int offset = get_branch_offset(old_fentry); + if (offset < 0) { + return ENOPATCHABLE; + } + + uint32_t branch = generate_branch_to_prologue(WITH_OFFSET(offset)); + char *dst = (char *)old_fentry + offset; + insnq_insert_write(dst, sizeof(branch), &branch); + + return 0; +} + + +/** @brief Actually patch the old function with the new function + * + * This function will finally patch the old function pointed by `old_faddr` + * with the one pointed by `new_faddr`, replacing the ulp NOP prologue with + * the intended content to redirect to the new function. + * + * @param old_faddr Address of the old function. + * @param new_faddr Address of the new function. + * @param enable False to disable the redirection to the new function. + * + * @return 0 if success, error code otherwise. + */ +int +ulp_patch_addr(void *old_faddr, void *new_faddr, int enable) +{ + unsigned char *dst = (unsigned char *) old_faddr; + + int ret = 0; + + if (enable) { + ulp_patch_prologue_layout(dst, new_faddr, ulp_prologue, 4*ULP_NOPS_LEN); + ret = ulp_patch_addr_trampoline(dst); + } else { + ret = ulp_skip_prologue(dst); + } + + return ret; +} diff --git a/lib/arch/powerpc64le/ulp_interface.S b/lib/arch/powerpc64le/ulp_interface.S new file mode 100644 index 000000000..18cd3ca0d --- /dev/null +++ b/lib/arch/powerpc64le/ulp_interface.S @@ -0,0 +1,131 @@ +/* + * libpulp - User-space Livepatching Library + * + * Copyright (C) 2017-2024 SUSE Software Solutions GmbH + * + * This file is part of libpulp. + * + * libpulp is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libpulp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libpulp. If not, see . + */ + +/* Prevent the stack from being needlessly set to executable. */ +.section .note.GNU-stack,"",%progbits + +.section .text,"ax",@progbits + +/* + * The functions below are the entry points into libpulp, which are + * accessed from libpulp's tools, such as the trigger tool. After + * ptracing into all threads, the tools modify the program counter so + * that it points to the third instruction in each of the functions + * below, i.e. right after the sequence of two nops. These two nops are + * skipped not because they must not be executed, but to account for + * syscall restarting by the kernel (which subtracts two from the + * program counter, so that, on regular syscalls, the syscall + * instructions gets executed again; in libpulp's case, the two nops get + * executed, without side-effects). + */ + +.global __ulp_revert_all + nop + nop +__ulp_revert_all: + nop + nop + addis 2, 12, .TOC.-__ulp_revert_all@ha + addi 2, 2, .TOC.-__ulp_revert_all@l + mflr %r0 + std %r0,16(%r1) + stdu %r1,-32(%r1) + bl __ulp_revert_patches_from_lib + nop + addi %r1,%r1,32 + ld %r0,16(%r1) + mtlr %r0 + trap + +.globl __ulp_trigger +.type __ulp_trigger, @function + nop + nop +__ulp_trigger: + nop + nop + addis 2, 12, .TOC.-__ulp_trigger@ha + addi 2, 2, .TOC.-__ulp_trigger@l + #.localentry __ulp_trigger,.-__ulp_trigger + mflr %r0 + std %r0,16(%r1) + stdu %r1,-32(%r1) + bl __ulp_apply_patch + nop + addi %r1,%r1,32 + ld %r0,16(%r1) + mtlr %r0 + trap + +.global __ulp_check_patched + nop + nop +__ulp_check_patched: + nop + nop + addis 2, 12, .TOC.-__ulp_check_patched@ha + addi 2, 2, .TOC.-__ulp_check_patched@l + mflr %r0 + std %r0,16(%r1) + stdu %r1,-32(%r1) + bl __ulp_check_applied_patch + nop + addi %r1,%r1,32 + ld %r0,16(%r1) + mtlr %r0 + trap + +.global __ulp_get_global_universe + nop + nop +__ulp_get_global_universe: + nop + nop + addis 2, 12, .TOC.-__ulp_get_global_universe@ha + addi 2, 2, .TOC.-__ulp_get_global_universe@l + mflr %r0 + std %r0,16(%r1) + stdu %r1,-32(%r1) + bl __ulp_get_global_universe_value + nop + addi %r1,%r1,32 + ld %r0,16(%r1) + mtlr %r0 + trap + + +.global __ulp_enable_or_disable_patching + nop + nop +__ulp_enable_or_disable_patching: + nop + nop + addis 2, 12, .TOC.-__ulp_enable_or_disable_patching@ha + addi 2, 2, .TOC.-__ulp_enable_or_disable_patching@l + mflr %r0 + std %r0,16(%r1) + stdu %r1,-32(%r1) + bl ulp_enable_or_disable_patching + nop + addi %r1,%r1,32 + ld %r0,16(%r1) + mtlr %r0 + trap diff --git a/lib/interpose.c b/lib/interpose.c index 80633b8ab..04934ce62 100644 --- a/lib/interpose.c +++ b/lib/interpose.c @@ -37,6 +37,7 @@ #include #include +#include "config.h" #include "ld_rtld.h" #include "msg_queue.h" #include "ulp.h" diff --git a/tests/Makefile.am b/tests/Makefile.am index af3e0f4de..d9f6efa57 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -560,7 +560,7 @@ manyprocesses_LDADD = libmanyprocesses.la manyprocesses_DEPENDENCIES = $(POST_PROCESS) $(METADATA) dlsym_SOURCES = dlsym.c -dlsym_LDADD = -lpthread -ldl -lrt -l:ld-linux-x86-64.so.2 +dlsym_LDADD = -lpthread -ldl -lrt -l:@_LD_LINUX@ dlsym_DEPENDENCIES = $(POST_PROCESS) $(METADATA) stress_SOURCES = stress.c diff --git a/tests/asunsafe_conversion.py b/tests/asunsafe_conversion.py index 0eba99b9a..b1ca5bf7e 100755 --- a/tests/asunsafe_conversion.py +++ b/tests/asunsafe_conversion.py @@ -34,7 +34,7 @@ errors = 0 try: # Apply the live patch. - child.livepatch('.libs/libblocked_livepatch1.so', retries=2000) + child.livepatch('.libs/libblocked_livepatch1.so', retries=100) except subprocess.TimeoutExpired: print('Deadlock while live patching - AS-Unsafe conversion not tested') # The deadlock test (tests/deadlock) has a far greater chance of diff --git a/tests/libpagecross_padding.S b/tests/libpagecross_padding.S index 875966c7f..74136c165 100644 --- a/tests/libpagecross_padding.S +++ b/tests/libpagecross_padding.S @@ -27,5 +27,4 @@ */ .set FILL_SIZE, PAGE_SIZE-16 .section .text -.align PAGE_SIZE .fill FILL_SIZE, 1, 0xCC diff --git a/tests/libtls.c b/tests/libtls.c index e7b11cf78..8eb725c74 100644 --- a/tests/libtls.c +++ b/tests/libtls.c @@ -29,6 +29,12 @@ banner_get(void) return banner; } +char ** +banner_get_ref(void) +{ + return &banner; +} + void banner_set(char *new) { diff --git a/tests/libtls_livepatch1.c b/tests/libtls_livepatch1.c index 435d7aa64..786225a16 100644 --- a/tests/libtls_livepatch1.c +++ b/tests/libtls_livepatch1.c @@ -32,6 +32,8 @@ static char *ulpr_string = "String from live patch"; void *__tls_get_addr(tls_index *); +#define SWAP(a,b) { typeof(a) _t = (a); (a) = (b); (b) = _t; } + void new_banner_set(__attribute__((unused)) char *new) { @@ -39,6 +41,7 @@ new_banner_set(__attribute__((unused)) char *new) errx(EXIT_FAILURE, "Live patch data references not initialized"); char **ulpr_banner = __tls_get_addr(&ti); + printf("module: %lx, offset: %lx\n", ti.ti_module, ti.ti_offset); printf("addr: 0x%lx\n", (unsigned long)ulpr_banner); *ulpr_banner = ulpr_string; } diff --git a/tests/process_access.c b/tests/process_access.c index 5ab510cd1..1900be453 100644 --- a/tests/process_access.c +++ b/tests/process_access.c @@ -26,19 +26,9 @@ static char *banner = "Original banner"; /* Noinline must be set, else no calls are issued. */ -char *__attribute__((noinline)) banner_get(void) { printf("banner addr: 0x%lx\n", (unsigned long)banner); return banner; } +char *__attribute__((noinline)) banner_get(void) { return banner; } - -/* Since clang-18 when compiling this function it is broken in two parts: - banner_set and banner_set.specialized.1. We don't want it on this test so - we disable optimizations in this test. */ -#ifdef __clang__ -#pragma clang optimize off -#endif void __attribute__((noinline)) banner_set(char *new) { banner = new; } -#ifdef __clang__ -#pragma clang optimize on -#endif int main(void) diff --git a/tests/redzone.c b/tests/redzone.c index 293c089a7..536139e0f 100644 --- a/tests/redzone.c +++ b/tests/redzone.c @@ -31,10 +31,10 @@ int main(void) { - long int counter = LOOPS; - long int result1; - long int result2; - long int result3; + volatile long int counter = LOOPS; + volatile long int result1; + volatile long int result2; + volatile long int result3; /* Signal readiness. */ printf("Waiting for input.\n"); @@ -47,6 +47,7 @@ main(void) */ /* clang-format off */ asm volatile ( +#if defined(__x86_64__) "movq $0, -0x08(%%rsp)\n" "movq $0, -0x78(%%rsp)\n" "movq $0, -0x80(%%rsp)\n" @@ -60,6 +61,29 @@ main(void) "movq -0x08(%%rsp), %0\n" "movq -0x78(%%rsp), %1\n" "movq -0x80(%%rsp), %2\n" +#elif defined(__powerpc64__) + "li %%r10, 0\n" + "stw %%r10, -0x08(%%r1)\n" + "stw %%r10, -0x78(%%r1)\n" + "stw %%r10, -0x80(%%r1)\n" + "loop:\n" + "li %%r10, 1\n" + "lwa %%r11, -0x08(%%r1)\n" + "add %%r11, %%r11, %%r10\n" + "stw %%r11, -0x08(%%r1)\n" + "lwa %%r11, -0x78(%%r1)\n" + "add %%r11, %%r11, %%r10\n" + "stw %%r11, -0x78(%%r1)\n" + "lwa %%r11, -0x80(%%r1)\n" + "add %%r11, %%r11, %%r10\n" + "stw %%r11, -0x80(%%r1)\n" + "addi %3, %3, -1\n" + "cmpdi %%cr0, %3, 0\n" + "bne %%cr0, loop\n" + "lwz %0, -0x08(%%r1)\n" + "lwz %1, -0x78(%%r1)\n" + "lwz %2, -0x80(%%r1)\n" +#endif : "=r"(result1), "=r"(result2), "=r"(result3), "+r"(counter) ); /* clang-format on */ diff --git a/tests/tls.c b/tests/tls.c index 9db05afa3..fa80f5685 100644 --- a/tests/tls.c +++ b/tests/tls.c @@ -31,7 +31,8 @@ static pthread_t threads[NUM_THREADS]; static pthread_barrier_t barrier; -char *banner_get(); +char *banner_get(void); +char **banner_get_ref(void); void banner_set(char *); /* On two threads this should work. If more, race conditions will appear. */ @@ -51,7 +52,7 @@ thread_func(void *arg) } /* Original banner. */ - printf("Banner addr: 0x%lX\n", (unsigned long)banner_get()); + printf("Banner addr: 0x%lX\n", (unsigned long)banner_get_ref()); printf("%s\n", banner_get()); sprintf(buf, "Banner changed from thread_func: %lu\n", i); diff --git a/tools/arch/powerpc64le/post-arch.c b/tools/arch/powerpc64le/post-arch.c new file mode 100644 index 000000000..30ff1d312 --- /dev/null +++ b/tools/arch/powerpc64le/post-arch.c @@ -0,0 +1,75 @@ +/* + * libpulp - User-space Livepatching Library + * + * Copyright (C) 2021-2023 SUSE Software Solutions GmbH + * + * This file is part of libpulp. + * + * libpulp is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libpulp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libpulp. If not, see . + */ + + +#include +#include +#include +#include +#include + +#include "ulp_common.h" +#include "post.h" +#include "ptrace.h" + +/* + * On POWER all instructions are 4 bytes long, so there is no need + * to do anything in the `ulp post` command. + */ +void +merge_nops_at_addr(Elf64_Addr addr, size_t amount) +{ + (void) addr; + (void) amount; +} + +/** @brief Check if function at `sym_address` has the NOP preamble. + * + * Functions that are livepatchable has ULP_NOPS_LEN - PRE_NOPS_LEN at the + * beginning of the function. Check the existence of this preamble. + * + * @param sym_address Address of function in target process. + * @param pid Pid of the target process. + * + * @return True if preamble exists, false if not. + */ +bool +check_preamble(ElfW(Addr) sym_address, pid_t pid) +{ + unsigned char bytes[12]; // 3 instructions + + if (read_memory((char *)bytes, sizeof(bytes), pid, sym_address)) { + /* In case it was unable to read the symbol due to permission error, just + * warn in debug output. */ + DEBUG("Unable to read symbol preamble at address %lx in process %d", + sym_address, pid); + return false; + } + + const unsigned char nop[] = { 0x00, 0x00, 0x00, 0x60 }; + + /* Check if first or third insn is a NOP.. */ + if (memcmp(bytes, nop, sizeof(nop)) == 0 || + memcmp(bytes + 8, nop, sizeof(nop)) == 0) { + return true; + } + return false; +}