summaryrefslogtreecommitdiff
path: root/test/threaded_execve.c
blob: 80726fe0165dfc149949c2bdd066e32855072680 (plain)
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
144
145
146
147
148
149
150
151
152
153
154
/*
 * Create NUM_THREADS threads which print "1" and sleep in pause().
 * Then create another thread which prints "2", and re-execs the program.
 * The leader then either sleeps in pause(), or exits if $LEADER_EXIT is set.
 * This triggers "execve'ed thread replaces thread leader" case.
 *
 * gcc -Wall -Os -o threaded_execve threaded_execve.c
 *
 * Try running it under strace like this:
 *
 * # Should not be confused by traced execve-ing thread
 * # replacing traced leader:
 * strace -oLOG -f ./threaded_execve
 *
 * # Same, but different output mode. Output after execve
 * # should go into leader's LOG.<pid> file, not into execve'ed
 * # thread's log file:
 * strace -oLOG -ff ./threaded_execve
 *
 * # Should not be confused by non-traced execve-ing thread
 * # replacing traced leader:
 * strace -oLOG ./threaded_execve
 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 * In Linux 3.2, non-traced execve-ing thread does not
 * become traced after execve, even though it has pid == leader's pid
 * after execve. And yet, strace's waitpid doesn't return ECHILD.
 *
 * # Run for NUM seconds, not just one second.
 * # Watch top to check for memory leaks in strace:
 * strace -oLOG -f ./threaded_execve <NUM>
 *
 */
#define NUM_THREADS 1

#define _GNU_SOURCE 1
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <asm/unistd.h>

/* Define clone2 for all arches */
#ifdef __ia64__
extern int __clone2(int (*fn) (void *), void *child_stack_base,
		size_t stack_size, int flags, void *arg, ...);
# define clone2 __clone2
#elif defined(__metag__)
# define clone2(func, stack_base, size, flags, arg...) \
	clone(func, stack_base, flags, arg)
#else
# define clone2(func, stack_base, size, flags, arg...) \
	clone(func, (stack_base) + (size), flags, arg)
#endif
/* Direct calls to syscalls, avoiding libc wrappers */
#define syscall_tgkill(pid, tid, sig) syscall(__NR_tgkill, (pid), (tid), (sig))
#define syscall_getpid() syscall(__NR_getpid)
#define syscall_gettid() syscall(__NR_gettid)
#define syscall_exit(v) syscall(__NR_exit, (v))

static char my_name[PATH_MAX];
static int leader_final_action;

static int
thread1(void *unused)
{
	write(1, "1", 1);
	for (;;)
		pause();
	return 0;
}

static int
thread2(void *unused)
{
	char buf[64];
	sprintf(buf, "%d", leader_final_action);
	write(1, "2", 1);
	usleep(20*1000);
	/* This fails with ENOENT if leader has exited by now! :) */
	execl("/proc/self/exe", "exe", "exe", buf, NULL);
	/* So fall back to resolved name */
	execl(my_name, "exe", "exe", buf, NULL);
	for (;;)
		pause();
	return 0;
}

static void
thread_leader(void)
{
	/* malloc gives sufficiently aligned buffer.
	 * long buf[] does not! (on ia64).
	 */
	int cnt = NUM_THREADS;
	while (--cnt >= 0) {
		/* As seen in pthread_create(): */
		clone2(thread1, malloc(16 * 1024), 16 * 1024, 0
			| CLONE_VM
			| CLONE_FS
			| CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM
			| 0        /* no signal to send on death */
			, NULL);
		usleep(20*1000);
	}
	clone2(thread2, malloc(16 * 1024), 16 * 1024, 0
		| CLONE_VM
		| CLONE_FS
		| CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM
		| 0        /* no signal to send on death */
		, NULL);

	/* Various states leader can be while other thread execve's: */
	switch (leader_final_action % 3) {
		case 0:
			syscall_exit(42); /* leader is dead */
		case 1:
			for (;;)
				pause(); /* leader is in syscall */
		default:
			for (;;)
				continue; /* leader is in userspace */
	}
}

int
main(int argc, char **argv)
{
	if (readlink("/proc/self/exe", my_name, sizeof(my_name)-1) <= 0)
		return 1;

	setbuf(stdout, NULL);

	if (argv[1] && strcmp(argv[1], "exe") == 0) {
		leader_final_action = atoi(argv[2]) + 1;
		thread_leader();
	}

	printf("%d: thread leader\n", getpid());

	alarm(argv[1] ? atoi(argv[1]) : 1);
	thread_leader();

	return 0;
}