Last active
May 10, 2026 10:18
-
-
Save StefanoBelli/2ff43fce56c7dcaab8fa83b4763edea0 to your computer and use it in GitHub Desktop.
Nasty things to do when creating a MT application without any support library like pthread
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // compile with gcc -O0 (no optimizations), | |
| // otherwise code is broken with CLONE_THREAD being enabled | |
| #define _GNU_SOURCE | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <sched.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <sys/wait.h> | |
| #include <sys/syscall.h> | |
| #include <sys/mman.h> | |
| //syscall instruction affects the following regs, see manuals | |
| #define __X86_SYSCALL_INSTR_CLOBBERS "rcx", "r11", "cc" | |
| #define __base_CLONE_THREAD_FLAGS \ | |
| CLONE_SIGHAND | \ | |
| CLONE_VM | \ | |
| CLONE_FS | \ | |
| CLONE_FILES | \ | |
| CLONE_PARENT_SETTID | |
| #ifndef _MORE_CTF | |
| #define _MORE_CTF | CLONE_THREAD | CLONE_CHILD_CLEARTID | |
| #endif | |
| #define CLONE_THREAD_FLAGS (__base_CLONE_THREAD_FLAGS _MORE_CTF) | |
| #define ctf_has(feature) (CLONE_THREAD_FLAGS & feature) | |
| #define REQUIRES_BUSYWAIT \ | |
| ctf_has(CLONE_THREAD) \ | |
| && \ | |
| !ctf_has(CLONE_CHILD_CLEARTID) | |
| //show config infos | |
| #ifdef _USE_SYSCALL_LIB | |
| # warning SYSCALL METHOD: using syscall() from glibc | |
| #else | |
| # warning SYSCALL METHOD: issuing raw syscall instruction | |
| #endif | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| # warning CLEARTID SUPPORT: enabled | |
| #else | |
| # warning CLEARTID SUPPORT: disabled | |
| #endif | |
| #if ctf_has(CLONE_THREAD) | |
| # warning CLONE_THREAD: enabled | |
| #else | |
| # warning CLONE_THREAD: disabled (waitpid needed) | |
| #endif | |
| #if REQUIRES_BUSYWAIT | |
| # warning WAITING METHOD: busy-wait | |
| #else | |
| # if ctf_has(CLONE_CHILD_CLEARTID) && ctf_has(CLONE_THREAD) | |
| # warning WAITING METHOD: futex | |
| # elif ctf_has(CLONE_CHILD_CLEARTID) && !ctf_has(CLONE_THREAD) | |
| # warning WAITING METHOD: futex + waitpid with WNOHANG | |
| # else | |
| # warning WAITING METHOD: waitpid | |
| # endif | |
| #endif | |
| #if ctf_has(CLONE_THREAD) || ctf_has(CLONE_CHILD_CLEARTID) | |
| # define full_membar() \ | |
| __asm__ __volatile__("mfence;" ::: "memory") | |
| #endif | |
| #if !ctf_has(CLONE_THREAD) | |
| # include <errno.h> | |
| #endif | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| # include <linux/futex.h> | |
| # define futex_wait(uaddr, val) \ | |
| syscall(SYS_futex, (uaddr), FUTEX_WAIT, (val), NULL, NULL, 0) | |
| #endif | |
| #define ull(x) ((unsigned long long)x) | |
| #ifdef _USE_SYSCALL_LIB | |
| # define klone(_flags, _stack, _parent_tid, _child_tid, _tid, ___kloned_label___) \ | |
| ({ \ | |
| int retval; \ | |
| __asm__ __volatile__( \ | |
| "movq %0, %%rdi\r\n" \ | |
| "movq %1, %%rsi\r\n" \ | |
| "movq %2, %%rdx\r\n" \ | |
| "movq %3, %%rcx\r\n" \ | |
| "movq %4, %%r8\r\n" \ | |
| "movq %5, %%r9\r\n" \ | |
| "call syscall\r\r" \ | |
| :: \ | |
| "i"(SYS_clone), \ | |
| "r"(ull(_flags)), \ | |
| "r"(ull(_stack)), \ | |
| "r"(ull(_parent_tid)), \ | |
| "r"(ull(_child_tid)), \ | |
| "r"(ull(_tid)) \ | |
| : \ | |
| "%rdi", \ | |
| "%rsi", \ | |
| "%rdx", \ | |
| "%rcx", \ | |
| "%r8" \ | |
| ); \ | |
| \ | |
| ___kloned_label___: \ | |
| \ | |
| __asm__ __volatile__( \ | |
| "movl %%eax, %0\r\n" \ | |
| : \ | |
| "=m"(retval) \ | |
| :: \ | |
| "%eax" \ | |
| ); \ | |
| retval; \ | |
| }) | |
| #else | |
| # define klone(_flags, _stack, _parent_tid, _child_tid, _tid, __unused__) \ | |
| ({ \ | |
| int retval; \ | |
| __asm__ __volatile__( \ | |
| "movq %1, %%rax\r\n" \ | |
| \ | |
| "movq %2, %%rdi\r\n" \ | |
| "movq %3, %%rsi\r\n" \ | |
| "movq %4, %%rdx\r\n" \ | |
| "movq %5, %%r10\r\n" \ | |
| "movq %6, %%r8\r\n" \ | |
| "syscall\r\n" \ | |
| \ | |
| "movl %%eax, %0\r\n" \ | |
| : \ | |
| "=m"(retval) \ | |
| : \ | |
| "i"(SYS_clone), \ | |
| "r"(ull(_flags)), \ | |
| "r"(ull(_stack)), \ | |
| "r"(ull(_parent_tid)), \ | |
| "r"(ull(_child_tid)), \ | |
| "r"(ull(_tid)) \ | |
| : \ | |
| "%rax", \ | |
| "%rdi", \ | |
| "%rsi", \ | |
| "%rdx", \ | |
| "%r10", \ | |
| "%r8", \ | |
| "%eax", \ | |
| __X86_SYSCALL_INSTR_CLOBBERS \ | |
| ); \ | |
| retval; \ | |
| }) | |
| #endif | |
| // libc exit()s may call exit_group instead!!! | |
| #define raw_thread_exit(code) \ | |
| do { \ | |
| __asm__ __volatile__( \ | |
| "movq $60, %%rax\r\n" \ | |
| "movl %0, %%edi\r\n" \ | |
| "syscall\r\n" \ | |
| :: \ | |
| "r"(code) \ | |
| : \ | |
| "%rax", \ | |
| "%edi", \ | |
| __X86_SYSCALL_INSTR_CLOBBERS \ | |
| ); \ | |
| } while(0) | |
| typedef void(*thread_fn)(void*); | |
| #define DEFAULT_STACK_SIZE (1024 * 1024) | |
| struct thread_struct { | |
| char* stack_area; | |
| size_t stack_size; | |
| pid_t child_tid; | |
| #if ctf_has(CLONE_THREAD) && !ctf_has(CLONE_CHILD_CLEARTID) | |
| int has_done; | |
| #elif ctf_has(CLONE_CHILD_CLEARTID) | |
| int* cleartid; | |
| #endif | |
| }; | |
| #define THREAD_CREATE_MMAP ((struct thread_struct*) -1) | |
| #define THREAD_CREATE_CLONE ((struct thread_struct*) -2) | |
| #define THREAD_CREATE_MALLOC ((struct thread_struct*) -3) | |
| #if REQUIRES_BUSYWAIT | |
| # define DEFINE_NEW_THREAD_WRAPPER(arg1, arg2, arg3, arg4) \ | |
| static void new_thread_wrapper(struct thread_struct **arg1, int *arg2, thread_fn arg3, void* arg4) | |
| # define CALL_NEW_THREAD_WRAPPER(arg1, arg2, arg3, arg4) \ | |
| new_thread_wrapper((arg1), (arg2), (arg3), (arg4)) | |
| #else | |
| # define DEFINE_NEW_THREAD_WRAPPER(arg1, arg2, arg3, arg4) \ | |
| static void new_thread_wrapper(thread_fn arg3, void *arg4) | |
| # define CALL_NEW_THREAD_WRAPPER(arg1, arg2, arg3, arg4) \ | |
| new_thread_wrapper((arg3), (arg4)) | |
| #endif | |
| //this is subtle: require a new base pointer | |
| DEFINE_NEW_THREAD_WRAPPER(new_thr, copied, fn, arg) { | |
| #if REQUIRES_BUSYWAIT | |
| while(!*new_thr) | |
| ; | |
| struct thread_struct *cpy_thr = *new_thr; | |
| full_membar(); | |
| *copied = 1; | |
| #endif | |
| fn(arg); | |
| #if REQUIRES_BUSYWAIT | |
| full_membar(); | |
| cpy_thr->has_done = 1; | |
| #endif | |
| raw_thread_exit(0); | |
| } | |
| static inline struct thread_struct *__thread_struct_alloc() { | |
| return (struct thread_struct*) malloc(sizeof(struct thread_struct)); | |
| } | |
| static inline void __thread_struct_free(struct thread_struct *thr) { | |
| free(thr); | |
| } | |
| static struct thread_struct* __thread_create(thread_fn fn, void* arg, size_t stack_size) { | |
| struct thread_struct *new_thr = NULL; | |
| #if REQUIRES_BUSYWAIT | |
| int copied = 0; | |
| #endif | |
| char* stack = (char*) mmap( | |
| NULL, | |
| stack_size, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, | |
| -1, 0); | |
| if(stack == MAP_FAILED) { | |
| return THREAD_CREATE_MMAP; | |
| } | |
| char* top_of_stack = stack + stack_size; | |
| #ifdef _USE_SYSCALL_LIB | |
| # define __copy_ith_byte(i) \ | |
| *((char*) top_of_stack + (i - 8)) = \ | |
| (char) ((ull(&&__new_thread_ret) >> (i * 8)) & 0xff) | |
| for(int i = 0; i < 8; i++) { | |
| __copy_ith_byte(i); | |
| } | |
| # undef __copy_ith_byte | |
| top_of_stack -= 8; | |
| #endif | |
| int *cleartid = NULL; | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| cleartid = (int*) malloc(sizeof(int)); | |
| if(!cleartid) { | |
| munmap(stack, stack_size); | |
| return THREAD_CREATE_MALLOC; | |
| } | |
| *cleartid = 1; | |
| full_membar(); | |
| #endif | |
| pid_t child_tid; | |
| int rv = klone(CLONE_THREAD_FLAGS, top_of_stack, &child_tid, cleartid, 0, __new_thread_ret); | |
| if(rv == -1) { | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| free(cleartid); | |
| #endif | |
| munmap(stack, stack_size); | |
| return THREAD_CREATE_CLONE; | |
| } else if(!rv) { | |
| CALL_NEW_THREAD_WRAPPER(&new_thr, &copied, fn, arg); | |
| } | |
| //this is needed for synchronization purposes | |
| #if REQUIRES_BUSYWAIT | |
| struct thread_struct* _tmp_thr = __thread_struct_alloc(); | |
| if(!_tmp_thr) { | |
| munmap(stack, stack_size); | |
| return THREAD_CREATE_MALLOC; | |
| } | |
| memset(_tmp_thr, 0, sizeof(struct thread_struct)); | |
| full_membar(); | |
| new_thr = _tmp_thr; | |
| #else | |
| new_thr = __thread_struct_alloc(); | |
| if(!new_thr) { | |
| # if ctf_has(CLONE_CHILD_CLEARTID) | |
| free(cleartid); | |
| # endif | |
| munmap(stack, stack_size); | |
| return THREAD_CREATE_MALLOC; | |
| } | |
| #endif | |
| new_thr->stack_area = stack; | |
| new_thr->stack_size = stack_size; | |
| new_thr->child_tid = child_tid; | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| new_thr->cleartid = cleartid; | |
| #endif | |
| #if REQUIRES_BUSYWAIT | |
| while(!copied) | |
| ; | |
| #endif | |
| return new_thr; | |
| } | |
| static struct thread_struct* thread_create(thread_fn fn, void* arg) { | |
| return __thread_create(fn, arg, DEFAULT_STACK_SIZE); | |
| } | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| # define __do_futex_wait(fut) \ | |
| do { \ | |
| futex_wait((fut), 1); \ | |
| free((fut)); \ | |
| (fut) = NULL; \ | |
| } while(0) | |
| #endif | |
| static void thread_join(struct thread_struct *thr) { | |
| #if !ctf_has(CLONE_THREAD) | |
| int wpid_flags = __WCLONE; | |
| # if ctf_has(CLONE_CHILD_CLEARTID) | |
| wpid_flags |= WNOHANG; | |
| __do_futex_wait(thr->cleartid); | |
| # endif | |
| if(waitpid(thr->child_tid, NULL, wpid_flags) < 0) { | |
| fprintf(stderr, "waitpid (%d): %s\n", thr->child_tid, strerror(errno)); | |
| abort(); | |
| } | |
| #else | |
| # if !ctf_has(CLONE_CHILD_CLEARTID) | |
| while(!thr->has_done) | |
| ; | |
| # else | |
| __do_futex_wait(thr->cleartid); | |
| # endif | |
| #endif | |
| munmap(thr->stack_area, thr->stack_size); | |
| __thread_struct_free(thr); | |
| } | |
| #if ctf_has(CLONE_CHILD_CLEARTID) | |
| # undef __do_futex_wait | |
| #endif | |
| #define boolstr(expr) ((expr) ? "true" : "false") | |
| static void hello_fn(void* arg) { | |
| char buf[8192]; | |
| memset(buf, 0, 8192); | |
| snprintf(buf, 8192, "thread %d of process %d, about to sleep: %s\n", | |
| gettid(), getpid(), boolstr(arg != (void*)-1)); | |
| write(STDOUT_FILENO, buf, strlen(buf)); | |
| if(arg != (void*)-1) { | |
| sleep(1); | |
| } | |
| } | |
| #undef boolstr | |
| int main() { | |
| hello_fn((void*)-1); | |
| struct thread_struct *thr1 = thread_create(hello_fn, NULL); | |
| struct thread_struct *thr2 = thread_create(hello_fn, NULL); | |
| thread_join(thr1); | |
| thread_join(thr2); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| SRC=clone.c | |
| BASE_CMD="gcc -O0 $SRC -o clone" | |
| banner() { | |
| echo "" | |
| echo " ---> Running config <--- " | |
| echo " Description: $@ " | |
| echo "" | |
| } | |
| run() { | |
| ./clone || exit 1 | |
| } | |
| # this doesn't work and idk why | |
| #banner "syscall lib" | |
| #$BASE_CMD -D_USE_SYSCALL_LIB -D_MORE_CTF="" | |
| #run | |
| # | |
| banner "syscall lib + thread/LWP numbering" | |
| $BASE_CMD -D_USE_SYSCALL_LIB -D_MORE_CTF="|CLONE_THREAD" | |
| run | |
| banner "syscall lib + cleartid/futex" | |
| $BASE_CMD -D_USE_SYSCALL_LIB -D_MORE_CTF="|CLONE_CHILD_CLEARTID" | |
| run | |
| banner "syscall lib + thread/LWP numbering + cleartid/futex" | |
| $BASE_CMD -D_USE_SYSCALL_LIB -D_MORE_CTF="|CLONE_THREAD|CLONE_CHILD_CLEARTID" | |
| run | |
| banner | |
| $BASE_CMD -D_MORE_CTF="" | |
| run | |
| banner "thread/LWP numbering" | |
| $BASE_CMD -D_MORE_CTF="|CLONE_THREAD" | |
| run | |
| banner "cleartid/futex" | |
| $BASE_CMD -D_MORE_CTF="|CLONE_CHILD_CLEARTID" | |
| run | |
| banner "thread/LWP numbering + cleartid/futex" | |
| $BASE_CMD -D_MORE_CTF="|CLONE_THREAD|CLONE_CHILD_CLEARTID" | |
| run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment