You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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:
The application calls the library function, e.g. foo;
foo's prologue saves %rdi on the stack;
foo's prologue writes a [hardcoded] index value to %rdi;
foo's prologue jumps to __ulp_prologue;
__ulp_prologue saves all caller-saved registers;
__ulp_prologue calls __ulp_manage_universes;
__ulp_manage_universes returns the address of the target function on %r11;
__ulp_prologue restores all saved registers;
__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>
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.
The text was updated successfully, but these errors were encountered:
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.
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 theint3
instruction, such as the following snippet, from lib/ulp_interface.S:where
__ulp_trigger
is the function Libpulp wants to call. Notice how it ends withint3
, notret
.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:
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: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.
The text was updated successfully, but these errors were encountered: