Skip to content

Commit

Permalink
Port libpulp to Powerpc64le
Browse files Browse the repository at this point in the history
This commit adds a new port to libpulp: Powerpc64le.

Signed-off-by: gbelinassi <[email protected]>
  • Loading branch information
giulianobelinassi committed Oct 14, 2024
1 parent 60ecba7 commit d80c9ad
Show file tree
Hide file tree
Showing 16 changed files with 545 additions and 23 deletions.
53 changes: 45 additions & 8 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,6 @@ AX_PYTHON_MODULE([psutil], [fatal])
AC_SUBST([AM_CFLAGS], ["-Wall -Wextra -Werror"])
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/"])],
[])

# Checking the call stack of all threads enables libpulp to only apply a live
# patch when no threads sit within the target library.
AC_ARG_ENABLE(stack-check,
Expand Down Expand Up @@ -223,14 +217,57 @@ 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=17
_PRE_NOPS_LEN=16
]
)

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] )

# 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 -Wl,--rpath=$with_glibc/"])],
[])

# 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
Expand Down
34 changes: 34 additions & 0 deletions include/arch/powerpc64le/arch_common.h
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions include/arch/x86_64/arch_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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.
Expand Down
222 changes: 222 additions & 0 deletions lib/arch/powerpc64le/patch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE

#include <stddef.h>
#include <string.h>
#include <limits.h>

#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] = {
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 prologue 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[0] = new_fentry_bytes[6];
prolog[1] = new_fentry_bytes[7];
prolog[8] = new_fentry_bytes[4];
prolog[9] = new_fentry_bytes[5];
prolog[16] = new_fentry_bytes[2];
prolog[17] = new_fentry_bytes[3];
prolog[28] = new_fentry_bytes[0];
prolog[29] = new_fentry_bytes[1];

/* Point to the prologue. */
char *fentry_prologue = old_fentry - INSN_SIZE * PRE_NOPS_LEN;
memwrite(fentry_prologue, prolog, INSN_SIZE * PRE_NOPS_LEN);
}

/** @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);
memwrite(dst, gNop, sizeof(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;
memwrite(dst, &branch, sizeof(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;
}
Loading

0 comments on commit d80c9ad

Please sign in to comment.