Concurrent programming the fast and dirty way!
Creating threads, mutexes, setting attributes and joining can be very easy using the pthreads library. However, there are times when you don’t want to link your program with another library or you need something fast. As an example you can see the pipe exploit where I need only two threads to trigger the race condition.
Here’s a simple way to create these threads. First, we need to allocate the stack for the thread using malloc(3) and then we can start it using clone(2).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
int koko(void *arg) { /* do something concurrently with main */ } int start_thread(int (*func)(void *), void *arg) { int thread_id; char *stack; if((stack = malloc(0x4000)) == NULL) { perror("malloc"); return -1; } if((thread_id = clone(func, stack + 0x4000 - sizeof(unsigned long), CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM | CLONE_THREAD, arg)) < 0) { perror("clone"); return -1; } return thread_id; } int main() { int thread_id; if((thread_id = startthread(koko, NULL)) == -1) { printf("Couldn't start thread.\n"); exit(1); } /* This runs together with koko */ } |
Using this code we create a new thread running in the same thread group. This means that calling getpid(2) will return the same pid. In order to distinguish threads in the same thread group you must use gettid(2) to get the thread id.
When creating a thread in the same thread group you cannot get the return value from the function you called. In order to get this value when it finishes you must create a thread in a new thread group which will be assigned a new pid and then call wait(2). To create such a thread you have to remove the CLONE_THREAD flag and replace it with SIGCHLD which will be the signal that will be send to the parent when the function returns. If we choose to send another signal, then wait(2) should be called with the __WALL or __WCLONE options.
These were the basics of creating threads. However, creating threads is only a part of concurrent programming. We will now create spinlocks using some internal functions of gcc. So, here are lock and unlock functions.
1 2 3 4 5 6 7 8 9 10 |
void lock(int spinlock) { while(__sync_lock_test_and_set(&spinlock, 1)) ; } void unlock(int spinlock) { __sync_lock_release(&spinlock); } |
It’s pretty simple using the functions gcc provides us. You can find more at the gcc documentation about atomic builtins.
It seems a black technique 🙂