Skip to content

Commit

Permalink
Add live patching examples in C++
Browse files Browse the repository at this point in the history
Libpulp provides livepatching capabilities that is general enough to
support many programming languages, not only C.  However, writing
livepatches in a language that allows more abstractions may not be
as straightfoward as in C.

To illustrate this we now provide the following examples in C++:

1-class: Show how C++ methods can be livepatched.
2-private_class: Show how private C++ functions can be livepatched.
3-indirect_call: Show how non-inlined private functions can be called
                 from a livepatch to avoid code growth
4-global_var: Show how a private global variable can be accessed from a
              livepatch
5-queue: Show how template methods can be livepatched.

Signed-off-by: Giuliano Belinassi <[email protected]>
  • Loading branch information
giulianobelinassi committed Jul 17, 2023
1 parent 12a51e3 commit f1faf19
Show file tree
Hide file tree
Showing 26 changed files with 1,100 additions and 0 deletions.
21 changes: 21 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Userspace Livepatch Examples.

This folder contain livepatching examples. Once you compile the example hitting
`make`, it will generate two files:

1. `test`
2. `a_livepatch1.so`

The first file is a binary and should be run with `libpulp.so` loaded. Assuming
libpulp is installed in `/usr/local/lib64/libpulp.so.0`, that is:
```
$ LD_PRELOAD=/usr/local/lib64/libpulp.so.0 ./test
```

The second file is the livepatch and should be applied with the `ulp` tool by
running:
```
$ ulp trigger a_livepatch.so
```

Have fun and happy livepatching!
21 changes: 21 additions & 0 deletions examples/cplusplus/1-class/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CXX=g++
CXXFLAGS=-O2 -fpatchable-function-entry=16,14 -fPIC -g3
ULP=/usr/bin/ulp
LDFLAGS=

all: test a_livepatch1.so

test: class.o
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

%.so: %.o %.dsc
$(CXX) $(CXXFLAGS) -shared -o $@ $<
$(ULP) packer $(word 2, $^)

%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $^ -o $@

clean:
rm -f test *.o *.so

clena: clean
15 changes: 15 additions & 0 deletions examples/cplusplus/1-class/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Example: Live Patching a C++ ordinary class
## About
In this test we have two files: `class.cpp` and `a_livepatch1.cpp`. The first
one contain code in C++ for a test program which print the contents of a
`point` class, where the second one contains a livepatch that modifies the
`Print` method so it prints content differently.

## Live Patching C++ methods
C++ methods can be live patched the same way as C functions. However, for
libpulp to find the symbols in the original target binary, you should write
the mangled name instead of the C++ original name of the method.

So instead of writing `Point3D::Print`, one should write `_ZN7Point3D5PrintEv`.
The original declaration of the class should be copied as well (see
`a_livepatch1.cpp` and `a_livepatch1.dsc`).
38 changes: 38 additions & 0 deletions examples/cplusplus/1-class/a_livepatch1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <iostream>

class Point
{
protected:
int x, y;

public:
Point(int x, int y);

int Get_X(void) const;
int Get_Y(void) const;

void Print_LP(void);
};

class Point3D : public Point
{
protected:
int z;

public:
Point3D(int x, int y, int z);

int Get_Z(void) const;

void Print_LP(void);
};

void Point::Print_LP(void)
{
std::cout << x + 1 << ' ' << y + 1 << '\n';
}

void Point3D::Print_LP(void)
{
std::cout << x + 1 << ' ' << y + 1 << ' ' << z << '\n';
}
3 changes: 3 additions & 0 deletions examples/cplusplus/1-class/a_livepatch1.dsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a_livepatch1.so
@./test
_ZN7Point3D5PrintEv:_ZN7Point3D8Print_LPEv
78 changes: 78 additions & 0 deletions examples/cplusplus/1-class/class.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include <iostream>
#include <unistd.h>

class Point
{
protected:
int x, y;

public:
Point(int x, int y);

int Get_X(void) const;
int Get_Y(void) const;

void Print(void);
};

class Point3D : public Point
{
protected:
int z;

public:
Point3D(int x, int y, int z);

int Get_Z(void) const;

void Print(void);
};

int Point::Get_X(void) const
{
return x;
}

int Point::Get_Y(void) const
{
return y;
}

// Will be livepatched
void Point::Print(void)
{
std::cout << Get_X() << ' ' << Get_Y() << '\n';
}

Point::Point(int x, int y)
{
this->x = x;
this->y = y;
}

Point3D::Point3D(int x, int y, int z) : Point(x, y)
{
this->z = z;
}

int Point3D::Get_Z(void) const
{
return z;
}

// Will be livepatched
void Point3D::Print(void)
{
std::cout << Get_X() << ' ' << Get_Y() << ' ' << Get_Z() << '\n';
}

int main(void)
{
Point3D p(3, 4, 5);
while (1) {
p.Print();
sleep(1);
}

return 0;
}
21 changes: 21 additions & 0 deletions examples/cplusplus/2-private_class/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CXX=g++
CXXFLAGS=-O2 -fpatchable-function-entry=16,14 -fdump-ipa-clones -fPIC -g3
ULP=/usr/bin/ulp
LDFLAGS=

all: test a_livepatch1.so

test: class.o
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

%.so: %.o %.dsc
$(CXX) $(CXXFLAGS) -shared -o $@ $<
$(ULP) packer $(word 2, $^)

%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $^ -o $@

clean:
rm -f test *.o *.so *.ipa-clones

clena: clean
46 changes: 46 additions & 0 deletions examples/cplusplus/2-private_class/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Example: Live Patching a C++ ordinary class
## About
This example illustrates how we can patch static (private), non-inlined functions.
Unfortunatelly, private functions are not publically exposed into the library you want to livepatch. Hence, those functions will not be present in the `.dynsym` table. Furthermore, the program can have multiple different functions with the same signature.

If the target program or library did not have its debug symbols stripped, it is possible to find the private symbols in the `.symtab` section. In this case we can use `readelf` to find the correct address of the function we want to patch. If not, we have to do the same analysis with the original binary `debuginfo`, hopefully distributed by your distribution.

## The example

In this example we have two files: `class.cpp` and `a_livepatch1.cpp`. The first file contains code to calculate the distance to the origin of the 3D point in question. Notice that the method `Norm` calls a private function `norm`, which is not inlined. In the second file there is a livepatch function which will replace `norm` with `norm3_lp`, which will compute the 3-norm instead of the 2-norm.

## Live Patching

This example is crafted so that the function `norm` is not inlined into `Norm`, as can be seen by the `noinline` declaration of it. If you remove this keyword then the function will be inlined and the only way to do the patching would be to livepatch all callers of `norm`. In this case it would be only `Norm`, but in other scenarios there could be thousands of occurences.

### Discovering if function was inlined.

We compile the example with `-fdump-ipa-clones`, which dumps Interprocedural Analysis decisions by GCC -- one being inlining decisions.

For `class.cpp`, a file is generated named `class.cpp.000i.ipa-clones` once you build the example. If you look for references of `norm` in this file you will see:
```
Callgraph clone;_ZL3dotPdS_;1394;class.cpp;8;15;_ZL4normPd;1395;class.cpp;13;24;inlining to
```
which translates that the function `dot` was *inlined into* `norm`. This is not a problem for us. There would only be a problem if `norm` was inlined somewhere, which is not the case.

### Retrieving the offset of target private function

A way to retrieve the target offset function is to use `readelf` to show the offset of each symbol in the target application/library. This piped with `grep` is enough to retrieve the symbol address if the function name is unique. If the function name is not unique then you should check if the function you desire to patch is actually at that address by looking into the assembly dump. If not, then you should proceed to the next occurence until you find it.

To list all offset of symbols matching `norm` in the `test` binary, do:
```
$ readelf -sW | grep 'norm'
```
output:
```
6: 000000000040122e 41 FUNC LOCAL DEFAULT 15 _ZL4normPd
```
This means that the function `_ZL4nromPd` (mangled name for `double norm(double v[3]);`) is available in offset `40122e` hexadecimal. it is also a function (`FUNC`) and it has `LOCAL` visibility.

### Description file with function offsets

The offset of the target function can be specified by appending an extra `:<offset>` to the livepatch symbol replacement specification:
```
_ZL4normPd:_Z8norm3_lpPd:40122e
```
This should be enough so that libpulp patches the correct function at that address.
8 changes: 8 additions & 0 deletions examples/cplusplus/2-private_class/a_livepatch1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <math.h>

typedef double vec3_t[3];

double norm3_lp(vec3_t v)
{
return cbrt(v[0]*v[0]*v[0] + v[1]*v[1]*v[1] + v[2]*v[2]*v[2]);
}
3 changes: 3 additions & 0 deletions examples/cplusplus/2-private_class/a_livepatch1.dsc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a_livepatch1.so
@./test
_ZL4normPd:_Z8norm3_lpPd:40122e
90 changes: 90 additions & 0 deletions examples/cplusplus/2-private_class/class.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <iostream>
#include <unistd.h>
#include <math.h>

typedef double vec3_t[3];
#define noinline __attribute__((noinline))

static double dot(vec3_t u, vec3_t v)
{
return u[0]*v[0] + u[1]*v[1] + u[2]*v[2];
}

// Will be livepatched.
static noinline double norm(vec3_t v)
{
return sqrt(dot(v, v));
}

class Point
{
protected:
double x, y;

public:
Point(double x, double y);

double Get_X(void) const;
double Get_Y(void) const;
};

class Point3D : public Point
{
protected:
double z;

public:
Point3D(double x, double y, double z);
double Norm(void);

double Get_Z(void) const;

};

double Point::Get_X(void) const
{
return x;
}

double Point::Get_Y(void) const
{
return y;
}

Point::Point(double x, double y)
{
this->x = x;
this->y = y;
}

Point3D::Point3D(double x, double y, double z) : Point(x, y)
{
this->z = z;
}

double Point3D::Get_Z(void) const
{
return z;
}

double Point3D::Norm(void)
{
vec3_t v = {x, y, z};
return norm(v);
}

extern "C" double some_function(vec3_t v)
{
return norm(v) * norm(v);
}

int main(void)
{
Point3D p(3, 4, 5);
while (1) {
std::cout << p.Norm() << '\n';
sleep(1);
}

return 0;
}
21 changes: 21 additions & 0 deletions examples/cplusplus/3-indirect_call/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CXX=g++
CXXFLAGS=-O2 -fpatchable-function-entry=16,14 -fdump-ipa-clones -fPIC -g3
ULP=/usr/bin/ulp
LDFLAGS=

all: test a_livepatch1.so

test: class.o
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)

%.so: %.o %.dsc
$(CXX) $(CXXFLAGS) -shared -o $@ $<
$(ULP) packer $(word 2, $^)

%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $^ -o $@

clean:
rm -f test *.o *.so *.ipa-clones

clena: clean
Loading

0 comments on commit f1faf19

Please sign in to comment.