RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / user / piethreads.c
4672 bytes ยท raw
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * 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 <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <time.h>
#include <unistd.h>

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;
}