/* * piethreads.c -- stage 19: a static PIE (stock `musl-gcc -static-pie`) * using pthreads, thread-local storage (PT_TLS), mutexes and stdio, plus * musl's ttyname() on a pseudo-terminal. Exit code = failed checks. * * Stage 20 builds the same file a second time as `execthreads`, with the * stock `musl-gcc -static` (a plain ET_EXEC at 0x400000), and both check * that the heap and the thread stacks (mmap) stay out of the image. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include static int fails; #define CHECK(cond, ...) \ do { \ if (!(cond)) { \ fails++; \ printf("piethreads: FAIL "); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) #define THREADS 4 #define ROUNDS 10000 static __thread int mine = 7; /* PT_TLS: initialized ... */ static __thread char scratch[64]; /* ... and zeroed (.tbss) */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static long shared; static FILE *out; extern const char __ehdr_start[]; /* first byte of the image */ extern char _end[]; /* one past the last byte */ /* Does [p, p + len) overlap our image? */ static int in_image(const void *p, unsigned long len) { unsigned long a = (unsigned long)p; return a < (unsigned long)_end && a + len > (unsigned long)__ehdr_start; } static void *worker(void *arg) { int on_stack; if (in_image(&on_stack, sizeof on_stack)) return (void *)1; /* the thread's stack (mmap) is inside the image */ long id = (long)arg; if (mine != 7 || scratch[0] != 0) return (void *)1; /* every thread starts with a fresh TLS copy */ mine = id; snprintf(scratch, sizeof scratch, "thread %ld", id); for (int i = 0; i < ROUNDS; i++) { pthread_mutex_lock(&lock); shared++; pthread_mutex_unlock(&lock); } pthread_mutex_lock(&lock); fprintf(out, "%s done\n", scratch); pthread_mutex_unlock(&lock); /* Nobody else changed our copy meanwhile. */ return (void *)(long)(mine != id); } static void threads(void) { const char *path = "/tmp/piethreads.txt"; out = fopen(path, "w"); CHECK(out != NULL, "fopen: errno %d", errno); CHECK(!in_image(out, 1), "FILE (malloc) inside the image"); void *big = malloc(1 << 20); /* big: musl mmaps it */ CHECK(big && !in_image(big, 1 << 20), "malloc(1 MiB) inside the image"); free(big); if (!out) return; pthread_t t[THREADS]; for (long i = 0; i < THREADS; i++) CHECK(pthread_create(&t[i], NULL, worker, (void *)i) == 0, "pthread_create %ld", i); for (int i = 0; i < THREADS; i++) { void *r; pthread_join(t[i], &r); CHECK(r == NULL, "thread %d saw wrong TLS or its stack in the image", i); } CHECK(shared == (long)THREADS * ROUNDS, "counter %ld", shared); CHECK(mine == 7, "main thread's TLS changed"); fclose(out); /* Read the file back through stdio: one line per thread. */ FILE *in = fopen(path, "r"); char line[64]; int lines = 0; while (in && fgets(line, sizeof line, in)) lines += strstr(line, " done\n") != NULL; if (in) fclose(in); CHECK(lines == THREADS, "%d lines in the file", lines); unlink(path); } /* musl's ttyname: readlink /proc/self/fd/N, then stat() that name and * fstat() the fd -- both must give the same device and inode (stage 19). */ static void tty_names(void) { int master = posix_openpt(O_RDWR | O_NOCTTY); CHECK(master >= 0, "posix_openpt: errno %d", errno); if (master < 0) return; CHECK(grantpt(master) == 0 && unlockpt(master) == 0, "grantpt/unlockpt"); char *name = ptsname(master); CHECK(name && strncmp(name, "/dev/pts/", 9) == 0, "ptsname %s", name ? name : "(null)"); int slave = name ? open(name, O_RDWR | O_NOCTTY) : -1; CHECK(slave >= 0, "open %s: errno %d", name, errno); if (slave >= 0) { char *tn = ttyname(slave); CHECK(tn && strcmp(tn, name) == 0, "ttyname(slave) = %s, errno %d", tn ? tn : "(null)", errno); struct stat st; CHECK(fstat(slave, &st) == 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == 136, "pts rdev"); close(slave); } char *tm = ttyname(master); CHECK(tm && strcmp(tm, "/dev/ptmx") == 0, "ttyname(master) = %s", tm ? tm : "(null)"); close(master); if (isatty(0)) { char *t0 = ttyname(0); CHECK(t0 != NULL, "ttyname(0): errno %d", errno); } } int main(void) { threads(); tty_names(); printf("piethreads: %d failed\n", fails); return fails; }