SUSE / libpulp

libpulp enables live patching in user space applications.
GNU Lesser General Public License v2.1
55 stars 11 forks source link

Port to other architectures #30

Open inconstante opened 3 years ago

inconstante commented 3 years ago

Currently, Libpulp only supports x86_64. Adding support for more architectures requires a few changes and a couple of new files. This issue tries to summarize what's needed:

1- Calling libpulp functions from ptrace:

Libpulp is like GDB. It attaches to running processes with ptrace and does stuff, such as calling functions in the target process. Calling functions from ptraced processes is not like calling functions in its own memory space, so Libpulp must hijack a running thread, stealing its context and diverting execution to wherever it wants. However, to regain control, it can't rely on regular ret instructions; instead, it must cause a software trap. On x86, that is achieved with the int3 instruction, such as the following snippet, from lib/ulp_interface.S:

__ulp_trigger:
    nop
    nop
    call    __ulp_apply_patch@PLT
    int3

where __ulp_trigger is the function Libpulp wants to call. Notice how it ends with int3, not ret.

A new port must provide a new lib/ulp_interface.S implementation.

2- Selecting between versions of a live patched function:

After a live patch has been applied, live patched functions have their prologues altered (see item 3, below), so that, instead of running the actual code of the function, execution gets diverted to a small block of code in its preamble that: saves the contents of %rdi (a caller-saved register) onto the stack; writes an index value to it; calls a live patch version selection routine, which performs its computation, then returns the result in %r11 (x86's scratch register on AMD64's ABI supplement for SysV).

Since the version selection routine executes between calls, it must preserve all registers, even caller-saved ones, specially those used in parameter passing. Otherwise, when the target function gets finally executed, the parameters would be wrong. In Libpulp, the saving and restoring of registers happens in lib/ulp_prologue.S.

Step-by-step, this is what happens in a call to a live patched function:

  1. The application calls the library function, e.g. foo;
  2. foo's prologue saves %rdi on the stack;
  3. foo's prologue writes a [hardcoded] index value to %rdi;
  4. foo's prologue jumps to __ulp_prologue;
  5. __ulp_prologue saves all caller-saved registers;
  6. __ulp_prologue calls __ulp_manage_universes;
  7. __ulp_manage_universes returns the address of the target function on %r11;
  8. __ulp_prologue restores all saved registers;
  9. __ulp_prologue jumps through %r11, which contais the address of the right version of foo.

A new port must provide a similar mechanism for its target architecture.

3- Patching function prologues

As mentioned above, live patched functions have their prologues altered, so that, instead of execution the function, as they normally would, they call a version selection routine. This function selection routine takes a single argument, an index into Libpulp's data structures, which contain references to all versions of the live patched functions.

However, when a live patchable process starts, the prologues of live patchable functions contain nop instructions, so that the regular functions execute normally. When a live patch is applied, these nops get replaced with a small block of code that diverts execution to the version selection routine. On x86_64, these blocks of code look like the following snippet:

prologue:
    push    %rdi
    movq    $index, %rdi
    jmp     0x0(%rip)
function:                  <-- this is the actual entry point to the function
    jmp     <prologue>

In the current implementation, Libpulp only knows how to write x64_64 code. The snippet above is written by ulp_patch_prologue_layout (also check ulp_prologue and ulp_patch_addr_absolute).

A new port must implement a mechanism that similarly patches function prologues.

4- compile specifically to emit a preamble and nops at function starts

on x86-64 that is GCCs -fpatchable-function-entry=N,M option. Using libpulp on other architecture means that this flag needs to be supported on that architecture.

susematz commented 3 years ago

4 - compile specifically to emit a preamble and nops at function starts

on x86-64 that is GCCs -fpatchable-function-entry=N,M option. Using libpulp on other architecture means that this flag needs to be supported on that architecture.

susematz commented 3 years ago

More items:

giulianobelinassi commented 1 year ago

S390x port is desired.