diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..58ec7ce8 --- /dev/null +++ b/examples/README.md @@ -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! diff --git a/examples/cplusplus/1-class/Makefile b/examples/cplusplus/1-class/Makefile new file mode 100644 index 00000000..8cf70ca8 --- /dev/null +++ b/examples/cplusplus/1-class/Makefile @@ -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 diff --git a/examples/cplusplus/1-class/README.md b/examples/cplusplus/1-class/README.md new file mode 100644 index 00000000..cfceaf2f --- /dev/null +++ b/examples/cplusplus/1-class/README.md @@ -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`). diff --git a/examples/cplusplus/1-class/a_livepatch1.cpp b/examples/cplusplus/1-class/a_livepatch1.cpp new file mode 100644 index 00000000..0362cea7 --- /dev/null +++ b/examples/cplusplus/1-class/a_livepatch1.cpp @@ -0,0 +1,38 @@ +#include + +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'; +} diff --git a/examples/cplusplus/1-class/a_livepatch1.dsc b/examples/cplusplus/1-class/a_livepatch1.dsc new file mode 100644 index 00000000..906682dc --- /dev/null +++ b/examples/cplusplus/1-class/a_livepatch1.dsc @@ -0,0 +1,3 @@ +a_livepatch1.so +@./test +_ZN7Point3D5PrintEv:_ZN7Point3D8Print_LPEv diff --git a/examples/cplusplus/1-class/class.cpp b/examples/cplusplus/1-class/class.cpp new file mode 100644 index 00000000..b0f588d4 --- /dev/null +++ b/examples/cplusplus/1-class/class.cpp @@ -0,0 +1,78 @@ +#include +#include + +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; +} diff --git a/examples/cplusplus/2-private_class/Makefile b/examples/cplusplus/2-private_class/Makefile new file mode 100644 index 00000000..8683a043 --- /dev/null +++ b/examples/cplusplus/2-private_class/Makefile @@ -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 diff --git a/examples/cplusplus/2-private_class/README.md b/examples/cplusplus/2-private_class/README.md new file mode 100644 index 00000000..32126066 --- /dev/null +++ b/examples/cplusplus/2-private_class/README.md @@ -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 `:` to the livepatch symbol replacement specification: +``` +_ZL4normPd:_Z8norm3_lpPd:40122e +``` +This should be enough so that libpulp patches the correct function at that address. diff --git a/examples/cplusplus/2-private_class/a_livepatch1.cpp b/examples/cplusplus/2-private_class/a_livepatch1.cpp new file mode 100644 index 00000000..17aa043f --- /dev/null +++ b/examples/cplusplus/2-private_class/a_livepatch1.cpp @@ -0,0 +1,8 @@ +#include + +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]); +} diff --git a/examples/cplusplus/2-private_class/a_livepatch1.dsc b/examples/cplusplus/2-private_class/a_livepatch1.dsc new file mode 100644 index 00000000..3a85f107 --- /dev/null +++ b/examples/cplusplus/2-private_class/a_livepatch1.dsc @@ -0,0 +1,3 @@ +a_livepatch1.so +@./test +_ZL4normPd:_Z8norm3_lpPd:40122e diff --git a/examples/cplusplus/2-private_class/class.cpp b/examples/cplusplus/2-private_class/class.cpp new file mode 100644 index 00000000..8c085ca2 --- /dev/null +++ b/examples/cplusplus/2-private_class/class.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +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; +} diff --git a/examples/cplusplus/3-indirect_call/Makefile b/examples/cplusplus/3-indirect_call/Makefile new file mode 100644 index 00000000..8683a043 --- /dev/null +++ b/examples/cplusplus/3-indirect_call/Makefile @@ -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 diff --git a/examples/cplusplus/3-indirect_call/README.md b/examples/cplusplus/3-indirect_call/README.md new file mode 100644 index 00000000..dae326d6 --- /dev/null +++ b/examples/cplusplus/3-indirect_call/README.md @@ -0,0 +1,46 @@ +# Example: Calling private non-inlined non-externalized function available in original binary +## About +This example illustrates how we can use code that is already available in the +original library. For this to work correctly, the function or method should not +have been inlined, so it is callable. + +## The example + +In this example we have two files: `class.cpp` and `a_livepatch1.cpp`. The +first file contains code to calculate the norm of a 2D point and print its +results. In the second file it contains a livepatch to replace the 2-norm +with a 3-Norm. The print call goes untouched, and since it is not inlined, +the original method can be called without problems. + +## Live Patching + +This example is crafted so that the method `Print` is not inlined into `Norm`, +as can be seen by the `noinline` declaration of it. + +### Reference to non-externalized symbol + +Since the symbol is not externalized, we have to get the reference to the +target function manually instead of relying on `ld` to find it to us. + +For us to be able to call this method, we must declare a pointer to a method +and call it. This pointer will be filled by libpulp with the address of the +desired method we want to call. + +In `a_livepatch.cpp`: +``` +extern "C" { + double (Point::*Print_LP)(double) = nullptr; +} +``` + +Then on `a_livepatch.dsc`: +``` +#_ZN5Point5PrintEd:Print_LP +``` + +The `#` token will describe that `Print_LP` should be initialized with the +*address* of Point::Print. In case the debug symbols are not available in +the binary, the offset of the symbol can be specified as: +``` +#_ZN5Point5PrintEd:Print_LP:4011fe +``` diff --git a/examples/cplusplus/3-indirect_call/a_livepatch1.cpp b/examples/cplusplus/3-indirect_call/a_livepatch1.cpp new file mode 100644 index 00000000..ce035a68 --- /dev/null +++ b/examples/cplusplus/3-indirect_call/a_livepatch1.cpp @@ -0,0 +1,31 @@ +#include + +class Point +{ + private: + void Print(double n); + + protected: + double x, y; + + public: + Point(double x, double y); + double Norm_LP(void); +}; + +/** This global variable will contain the address of Point::Print once the + livepatch is installed. */ +extern "C" { + double (Point::*Print_LP)(double) = nullptr; +} + + +double Point::Norm_LP(void) +{ + double n = cbrt(x*x*x + y*y*y); + + /** Since we declare it as a function, we must explicitely pass the 'this' + pointer. */ + (this->*Print_LP)(n); + return n; +} diff --git a/examples/cplusplus/3-indirect_call/a_livepatch1.dsc b/examples/cplusplus/3-indirect_call/a_livepatch1.dsc new file mode 100644 index 00000000..97dd43cd --- /dev/null +++ b/examples/cplusplus/3-indirect_call/a_livepatch1.dsc @@ -0,0 +1,4 @@ +a_livepatch1.so +@./test +_ZN5Point4NormEv:_ZN5Point7Norm_LPEv +#_ZN5Point5PrintEd:Print_LP diff --git a/examples/cplusplus/3-indirect_call/class.cpp b/examples/cplusplus/3-indirect_call/class.cpp new file mode 100644 index 00000000..307b7a4a --- /dev/null +++ b/examples/cplusplus/3-indirect_call/class.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#define noinline __attribute__((noinline)) + +class Point +{ + private: + void noinline Print(double n); + + protected: + double x, y; + + public: + Point(double x, double y); + double Norm(void); +}; + +Point::Point(double x, double y) +{ + this->x = x; + this->y = y; +} + +// Will be livepatched; +double Point::Norm(void) +{ + double n = sqrt(x*x + y*y); + Print(n); + return n; +} + +void noinline Point::Print(double n) +{ + std::cout << "Point: " << x << ' ' << y << ' ' << "Have norm2 = " << n <<'\n'; +} + +int main(void) +{ + Point p(3, 4); + while (1) { + p.Norm(); + sleep(1); + } + + return 0; +} diff --git a/examples/cplusplus/4-global_var/Makefile b/examples/cplusplus/4-global_var/Makefile new file mode 100644 index 00000000..020f34a4 --- /dev/null +++ b/examples/cplusplus/4-global_var/Makefile @@ -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: test.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 diff --git a/examples/cplusplus/4-global_var/README.md b/examples/cplusplus/4-global_var/README.md new file mode 100644 index 00000000..9491b0b0 --- /dev/null +++ b/examples/cplusplus/4-global_var/README.md @@ -0,0 +1,33 @@ +# Example: Calling private non-inlined non-externalized function available in original binary +## About + +This example illustrates how we can access global variables -- private or +public -- as it may be necessary to create a livepatch. One case where such cases +may happen is to access global locks, as shown in this example. + +In `test.cpp`, there is an accumulator that is protected by a mutex lock. A +livepatch that touches this critical section will have to lock it before doing +any modifications to the `sum` variable. + +## The example + +In this example we have two files: `test.cpp` and `a_livepatch1.cpp`. The +first file contains code to accumulate into a variable using multiple +threads. The second file contains a patch that set that variable to 0. + +## Live Patching + +In order to create this livepatch, we have to setup references to the global +variables that needs to be modified. On `a_livepatch.cpp`: +``` +volatile long *sum_ptr = NULL; +pthread_mutex_t *sum_lock_ptr = NULL; +``` +Then on .dsc: +``` +#sum:sum_ptr +#sum_lock:sum_lock_ptr +``` + +with this, the pointers `sum_ptr` and `sum_lock_ptr` will be initialized with +the address of `sum` and `sum_lock`. diff --git a/examples/cplusplus/4-global_var/a_livepatch1.cpp b/examples/cplusplus/4-global_var/a_livepatch1.cpp new file mode 100644 index 00000000..84c244cd --- /dev/null +++ b/examples/cplusplus/4-global_var/a_livepatch1.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +/* Pointer to the original sum variable. This will be initialized with the + address of `sum` when the patch is loaded in the program, as described in + the .dsc file. */ +volatile long *sum_ptr = NULL; + +/* Pointer to the original lock. */ +pthread_mutex_t *sum_lock_ptr = NULL; + +void accumulate_2(long x __attribute__((unused))) +{ + assert(sum_ptr && sum_lock_ptr); + + if (pthread_mutex_lock(sum_lock_ptr) != 0) { + abort(); + } + + *sum_ptr = 0; + + if (pthread_mutex_unlock(sum_lock_ptr) != 0) { + abort(); + } +} diff --git a/examples/cplusplus/4-global_var/a_livepatch1.dsc b/examples/cplusplus/4-global_var/a_livepatch1.dsc new file mode 100644 index 00000000..72fe8ffd --- /dev/null +++ b/examples/cplusplus/4-global_var/a_livepatch1.dsc @@ -0,0 +1,5 @@ +a_livepatch1.so +@./test +_Z10accumulatel:_Z12accumulate_2l +#sum:sum_ptr +#sum_lock:sum_lock_ptr diff --git a/examples/cplusplus/4-global_var/test.cpp b/examples/cplusplus/4-global_var/test.cpp new file mode 100644 index 00000000..163fcb72 --- /dev/null +++ b/examples/cplusplus/4-global_var/test.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#define noinline __attribute__((noinline)) +#define NUM_ACCUMULATORS 4 + +volatile long sum; +pthread_mutex_t sum_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Accumulate into a global variable. */ +// Will be livepatched; +void noinline accumulate(long x) +{ + if (pthread_mutex_lock(&sum_lock) != 0) { + abort(); + } + + sum += x; + + if (pthread_mutex_unlock(&sum_lock) != 0) { + abort(); + } +} + +void *accumulator(void* x) +{ + for (long i = 0; i < 100000000L; i++) { + accumulate(i); + } + + return NULL; +} + +int main(void) +{ + pthread_t threads[NUM_ACCUMULATORS]; + + for (int i = 0; i < NUM_ACCUMULATORS; i++) { + if (pthread_create(&threads[i], NULL, accumulator, NULL) != 0) { + abort(); + } + } + + for (int i = 0; i < NUM_ACCUMULATORS; i++) { + if (pthread_join(threads[i], NULL) != 0) { + abort(); + } + } + + printf("sum = %ld\n", sum); + + return 0; +} diff --git a/examples/cplusplus/5-queue/Makefile b/examples/cplusplus/5-queue/Makefile new file mode 100644 index 00000000..77a10020 --- /dev/null +++ b/examples/cplusplus/5-queue/Makefile @@ -0,0 +1,21 @@ +CXX=g++ +CXXFLAGS=-fpatchable-function-entry=16,14 -fdump-ipa-clones -fPIC -g3 -Wno-terminate -O2 +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 diff --git a/examples/cplusplus/5-queue/README.md b/examples/cplusplus/5-queue/README.md new file mode 100644 index 00000000..637a2531 --- /dev/null +++ b/examples/cplusplus/5-queue/README.md @@ -0,0 +1,39 @@ +# Example: Live patching template methods +## About + +This example illustrates how we can livepatch template methods in C++. + +### C++ templates + +C++ has the ability to generate code according to given parameters by using +`templates`. This is more powerful than C macros and it is very useful to +generate classes or functions for multiple types or multiple predetermined +bounds. + +## The example + +In this example we have two files: `class.cpp` and `a_livepatch1.cpp`. The +first file contains a template class `Queue` which generates code for each +type and maximum bound. Here we only use the case where MAX = 32 and types +as `long` and `double`. In the second file we have a livepatch which modifies +the code of `Push` to print a message for each type it runs. + +Since the `Queue` is used with two types (`long` and `double`), we have to +generate two functions in the livepatch library. This is shown by the +declarations: +``` +/* Output the modified functions for all types generated. */ +template void Queue::Push_LP(long x); +template void Queue::Push_LP(double x); +``` + +which will generate two functions `_ZN5QueueILi32ElE7Push_LPEl` and +`_ZN5QueueILi32EdE7Push_LPEd`. + +## Live Patching + +Unfortunately since C++ `template` functions are generating according to each +one of the template parameters that are given, you must generate one function +for each one parameter that are used in the program. On our example it is easy, +as the project has one file, the template is expanded to only two types, and +functions are not inlined. diff --git a/examples/cplusplus/5-queue/a_livepatch1.cpp b/examples/cplusplus/5-queue/a_livepatch1.cpp new file mode 100644 index 00000000..ba67fc84 --- /dev/null +++ b/examples/cplusplus/5-queue/a_livepatch1.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +#define QMAX 32 + +/** A producer-consumer queue. This structure creates a channel in which + two threads can communicate, one by enqueuing elements and another by + dequeuing elements. */ +template +class Queue +{ + public: + Queue(void); + ~Queue(void); + + void Push_LP(T x); + T Pop(void); + + protected: + /** Position of the last inserted element.*/ + int head; + + /** Position of the oldest element in the queue. */ + int tail; + + /** Semaphore that will block any attempt of dequeuing an element if the + queue is empty. */ + sem_t empty; + + /** Semaphore that will block any attempt of enqueuing an element if the + queue is full. */ + sem_t full; + + /** Lock for head & tail. This is unnecessary if there is only one producer + and one consumer. If you wish to support many producers and many + consumers, define MORE_THAN_ONE_ONE. */ + pthread_mutex_t lock; + + /** The queue buffer. */ + T elem[MAX]; +}; + +static bool already_print_l = false; +static bool already_print_d = false; + +template +void Queue::Push_LP(T x) +{ + int ret; + /* Block if the queue is full. */ + ret = sem_wait(&full); + if (ret != 0) { + throw "semaphore error"; + } + + /* Acquire lock of queue. */ + ret = pthread_mutex_lock(&lock); + if (ret != 0) { + throw "mutex lock error"; + } + /* ----------------------------- */ + + if (typeid(T) == typeid(long) && already_print_l == false) { + std::cout << "from critical section with type long\n"; + already_print_l = true; + } else if (typeid(T) == typeid(double) && already_print_d == false) { + std::cout << "from critical section with type double\n"; + already_print_d = true; + } + + elem[head++] = x; + + /* Wraps around if end of buffer. */ + if (head >= MAX) { + head = 0; + } + + /* ----------------------------- */ + + /* Release lock of queue. */ + ret = pthread_mutex_unlock(&lock); + if (ret != 0) { + throw "mutex release error"; + } + + /* Alert other threads that we inserted something. */ + ret = sem_post(&empty); + if (ret != 0) { + throw "semaphore post error"; + } +} + +/* Output the modified functions for all types generated. */ +template void Queue::Push_LP(long x); +template void Queue::Push_LP(double x); diff --git a/examples/cplusplus/5-queue/a_livepatch1.dsc b/examples/cplusplus/5-queue/a_livepatch1.dsc new file mode 100644 index 00000000..899d4c0f --- /dev/null +++ b/examples/cplusplus/5-queue/a_livepatch1.dsc @@ -0,0 +1,4 @@ +a_livepatch1.so +@./test +_ZN5QueueILi32ElE4PushEl:_ZN5QueueILi32ElE7Push_LPEl +_ZN5QueueILi32EdE4PushEd:_ZN5QueueILi32EdE7Push_LPEd diff --git a/examples/cplusplus/5-queue/class.cpp b/examples/cplusplus/5-queue/class.cpp new file mode 100644 index 00000000..24eccceb --- /dev/null +++ b/examples/cplusplus/5-queue/class.cpp @@ -0,0 +1,305 @@ +#include +#include +#include +#include +#include +#include + +#define noinline __attribute__((noinline)) +#define END_TOKEN (1 << 30) +#define END 10000000L + +#define NUM_CONSUMERS 1 +#define NUM_PRODUCERS 1 + +#define QMAX 32 + +/** A producer-consumer queue. This structure creates a channel in which + two threads can communicate, one by enqueuing elements and another by + dequeuing elements. */ +template +class Queue +{ + public: + Queue(void); + ~Queue(void); + + void Push(T x); + T Pop(void); + + protected: + /** Position of the last inserted element.*/ + int head; + + /** Position of the oldest element in the queue. */ + int tail; + + /** Semaphore that will block any attempt of dequeuing an element if the + queue is empty. */ + sem_t empty; + + /** Semaphore that will block any attempt of enqueuing an element if the + queue is full. */ + sem_t full; + + /** Lock for head & tail. This is unnecessary if there is only one producer + and one consumer. If you wish to support many producers and many + consumers, define MORE_THAN_ONE_ONE. */ + pthread_mutex_t lock; + + /** The queue buffer. */ + T elem[MAX]; +}; + +template +Queue::Queue(void) + : head(0), + tail(0) +{ + //memset(this, 0, sizeof(*this)); + + int ret; + + ret = pthread_mutex_init(&lock, NULL); + if (ret != 0) { + throw "failed initializing mutex lock"; + } + + ret = sem_init(&empty, 0, 0); + if (ret != 0) { + throw "failed initializing semaphore"; + } + + ret = sem_init(&full, 0, MAX); + if (ret != 0) { + throw "failed initializing semaphore"; + } +} + +template +Queue::~Queue(void) +{ + int ret; + + ret = pthread_mutex_destroy(&lock); + if (ret != 0) { + throw "failed deinitializing lock"; + } + + ret = sem_destroy(&full); + if (ret != 0) { + throw "failed deinitializing semaphore"; + } + + ret = sem_destroy(&empty); + if (ret != 0) { + throw "failed deinitializing semaphore"; + } +} + +// Will be livepatched. +template +void Queue::Push(T x) +{ + int ret; + /* Block if the queue is full. */ + ret = sem_wait(&full); + if (ret != 0) { + throw "semaphore error"; + } + + /* Acquire lock of queue. */ + ret = pthread_mutex_lock(&lock); + if (ret != 0) { + throw "mutex lock error"; + } + /* ----------------------------- */ + + elem[head++] = x; + + /* Wraps around if end of buffer. */ + if (head >= MAX) { + head = 0; + } + + /* ----------------------------- */ + + /* Release lock of queue. */ + ret = pthread_mutex_unlock(&lock); + if (ret != 0) { + throw "mutex release error"; + } + + /* Alert other threads that we inserted something. */ + ret = sem_post(&empty); + if (ret != 0) { + throw "semaphore post error"; + } +} + +template +T Queue::Pop(void) +{ + T ret; + + /* Block if the queue is empty. */ + if (sem_wait(&empty) != 0) { + throw "semaphore wait error"; + } + + /* Acquire lock of queue. */ + if (pthread_mutex_lock(&lock) != 0) { + throw "mutex lock error"; + } + + /* ----------------------------- */ + + ret = elem[tail++]; + + /* Wraps around if end of buffer. */ + if (tail >= MAX) { + tail = 0; + } + + /* ----------------------------- */ + + /* Release lock of queue. */ + if (pthread_mutex_unlock(&lock) != 0) { + throw "mutex unlock error"; + } + + /* Alert other threads that we inserted something. */ + if (sem_post(&full) != 0) { + throw "semaphore post error"; + } + + return ret; +} + +void *consumer(void *p) +{ + Queue *q = (Queue *) p; + long x; + + while ((x = q->Pop()) != END_TOKEN) { + } + + return nullptr; +} + +void *producer(void *p) +{ + Queue *q = (Queue *) p; + + for (long i = 0; i < END; i++) { + q->Push(i); + } + q->Push(END_TOKEN); + + return nullptr; +} + +int long_queue(void) +{ + Queue q; + pthread_t producers[NUM_PRODUCERS]; + pthread_t consumers[NUM_CONSUMERS]; + + for (int i = 0; i < NUM_PRODUCERS; i++) { + if (pthread_create(&producers[i], NULL, producer, (void*) &q) != 0) { + std::cout << "Error creating producer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_CONSUMERS; i++) { + if (pthread_create(&consumers[i], NULL, consumer, (void*) &q) != 0) { + std::cout << "Error creating consumer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_PRODUCERS; i++) { + if (pthread_join(producers[i], NULL) != 0) { + std::cout << "Error joining producer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_CONSUMERS; i++) { + if (pthread_join(consumers[i], NULL) != 0) { + std::cout << "Error joining consumers thread " << i << '\n'; + return 1; + } + } + + return 0; +} + + +void *consumer_dbl(void *p) +{ + Queue *q = (Queue *) p; + double x; + + while ((x = q->Pop()) != (1./0.)) { + } + + return nullptr; +} + +void *producer_dbl(void *p) +{ + Queue *q = (Queue *) p; + + for (long i = 0; i < END; i++) { + q->Push((double)i); + } + q->Push((double)(1./0.)); + + return nullptr; +} + +int double_queue(void) +{ + Queue q; + pthread_t producers[NUM_PRODUCERS]; + pthread_t consumers[NUM_CONSUMERS]; + + for (int i = 0; i < NUM_PRODUCERS; i++) { + if (pthread_create(&producers[i], NULL, producer_dbl, (void*) &q) != 0) { + std::cout << "Error creating producer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_CONSUMERS; i++) { + if (pthread_create(&consumers[i], NULL, consumer_dbl, (void*) &q) != 0) { + std::cout << "Error creating consumer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_PRODUCERS; i++) { + if (pthread_join(producers[i], NULL) != 0) { + std::cout << "Error joining producer thread " << i << '\n'; + return 1; + } + } + + for (int i = 0; i < NUM_CONSUMERS; i++) { + if (pthread_join(consumers[i], NULL) != 0) { + std::cout << "Error joining consumers thread " << i << '\n'; + return 1; + } + } + + return 0; +} + +int main(void) +{ + long_queue(); + double_queue(); + return 0; +}