xref: /xnu-11215/tests/stackshot_accuracy.m (revision 8d741a5d)
1#include <darwintest.h>
2#include <darwintest_utils.h>
3#include <sys/kern_memorystatus.h>
4#include <kern/debug.h>
5#include <mach-o/dyld.h>
6#include <sys/stackshot.h>
7#include <kdd.h>
8#include <signal.h>
9#include "test_utils.h"
10
11#define RECURSIONS 25
12#define FIRST_RECURSIVE_FRAME 3
13
14T_GLOBAL_META(
15		T_META_NAMESPACE("xnu.stackshot.accuracy"),
16		T_META_RADAR_COMPONENT_NAME("xnu"),
17		T_META_RADAR_COMPONENT_VERSION("stackshot"),
18		T_META_OWNER("jonathan_w_adams"),
19		T_META_CHECK_LEAKS(false),
20		T_META_ASROOT(true),
21		XNU_T_META_SOC_SPECIFIC
22		);
23
24
25void child_init(void);
26void parent_helper_singleproc(int);
27
28#define CHECK_FOR_FAULT_STATS         (1 << 0)
29#define WRITE_STACKSHOT_BUFFER_TO_TMP (1 << 1)
30#define CHECK_FOR_KERNEL_THREADS      (1 << 2)
31int check_stackshot(void *, int);
32
33/* used for WRITE_STACKSHOT_BUFFER_TO_TMP */
34static char const *current_scenario_name;
35static pid_t child_pid;
36
37/* helpers */
38
39static void __attribute__((noinline))
40child_recurse(int r, int spin, void (^cb)(void))
41{
42	if (r > 0) {
43		child_recurse(r - 1, spin, cb);
44	}
45
46	cb();
47
48	/* wait forever */
49	if (spin == 0) {
50		sleep(100000);
51	} else if (spin == 2) {
52		int v = 1;
53		/* ssh won't let the session die if we still have file handles open to its output. */
54		close(STDERR_FILENO);
55		close(STDOUT_FILENO);
56		T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.wedge_thread", NULL, NULL, &v, sizeof(v)),
57					"wedged thread in the kernel");
58	} else {
59		while (1) {
60			__asm__ volatile("" : : : "memory");
61		}
62	}
63}
64
65T_HELPER_DECL(simple_child_process, "child process that will be frozen and others")
66{
67	child_init();
68}
69
70T_HELPER_DECL(sid_child_process, "child process that setsid()s")
71{
72	pid_t ppid = getppid();
73
74	T_ASSERT_POSIX_SUCCESS(setsid(), "session id set");
75
76	child_recurse(RECURSIONS, 2, ^{
77		kill(ppid, SIGUSR1);
78	});
79
80	T_ASSERT_FAIL("child_init returned!");
81}
82
83static void
84kill_children(void)
85{
86	kill(child_pid, SIGKILL);
87}
88
89static void *
90take_stackshot(pid_t target_pid, uint64_t extra_flags, uint64_t since_timestamp)
91{
92	void *stackshot_config;
93	int err, retries = 5;
94	uint64_t stackshot_flags = STACKSHOT_KCDATA_FORMAT |
95								STACKSHOT_THREAD_WAITINFO |
96								STACKSHOT_GET_DQ;
97
98	/* we should be able to verify delta stackshots */
99	if (since_timestamp != 0) {
100		stackshot_flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT;
101	}
102
103	stackshot_flags |= extra_flags;
104
105	stackshot_config = stackshot_config_create();
106	T_ASSERT_NOTNULL(stackshot_config, "allocate stackshot config");
107
108	err = stackshot_config_set_flags(stackshot_config, stackshot_flags);
109	T_ASSERT_EQ(err, 0, "set flags on stackshot config");
110
111	err = stackshot_config_set_pid(stackshot_config, target_pid);
112	T_ASSERT_EQ(err, 0, "set target pid on stackshot config");
113
114	if (since_timestamp != 0) {
115		err = stackshot_config_set_delta_timestamp(stackshot_config, since_timestamp);
116		T_ASSERT_EQ(err, 0, "set prev snapshot time on stackshot config");
117	}
118
119	while (retries > 0) {
120		err = stackshot_capture_with_config(stackshot_config);
121		if (err == 0) {
122			break;
123		} else if (err == EBUSY || err == ETIMEDOUT) {
124			T_LOG("stackshot capture returned %d (%s)\n", err, strerror(err));
125			if (retries == 0) {
126				T_ASSERT_FAIL("failed to take stackshot with error after retries: %d: %s\n", err, strerror(err));
127			}
128
129			retries--;
130			continue;
131		} else {
132			T_ASSERT_FAIL("failed to take stackshot with error: %d: %s\n", err, strerror(err));
133		}
134	}
135
136	return stackshot_config;
137}
138
139int
140check_stackshot(void *stackshot_config, int flags)
141{
142	void *buf;
143	uint32_t buflen, kcdata_type;
144	kcdata_iter_t iter;
145	NSError *nserror = nil;
146	pid_t target_pid;
147	int ret = 0;
148	uint64_t expected_return_addr = 0;
149	bool found_fault_stats = false;
150	struct stackshot_fault_stats fault_stats = {0};
151
152	buf = stackshot_config_get_stackshot_buffer(stackshot_config);
153	T_ASSERT_NOTNULL(buf, "stackshot buffer is not null");
154	buflen = stackshot_config_get_stackshot_size(stackshot_config);
155	T_ASSERT_GT(buflen, 0, "valid stackshot buffer length");
156	target_pid = ((struct stackshot_config*)stackshot_config)->sc_pid;
157	T_ASSERT_GT(target_pid, 0, "valid target_pid");
158
159	/* if need to write it to fs, do it now */
160	if (flags & WRITE_STACKSHOT_BUFFER_TO_TMP) {
161		char sspath[MAXPATHLEN];
162		strlcpy(sspath, current_scenario_name, sizeof(sspath));
163		strlcat(sspath, ".kcdata", sizeof(sspath));
164		T_QUIET; T_ASSERT_POSIX_ZERO(dt_resultfile(sspath, sizeof(sspath)),
165				"create result file path");
166
167		FILE *f = fopen(sspath, "w");
168		T_WITH_ERRNO; T_QUIET; T_ASSERT_NOTNULL(f,
169				"open stackshot output file");
170
171		size_t written = fwrite(buf, buflen, 1, f);
172		T_QUIET; T_ASSERT_POSIX_SUCCESS(written, "wrote stackshot to file");
173
174		fclose(f);
175	}
176
177	/* begin iterating */
178	iter = kcdata_iter(buf, buflen);
179	T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "buffer is a stackshot");
180
181	/* time to iterate */
182	iter = kcdata_iter_next(iter);
183	KCDATA_ITER_FOREACH(iter) {
184		kcdata_type = kcdata_iter_type(iter);
185		NSNumber *parsedPid;
186		NSMutableDictionary *parsedContainer, *parsedThreads;
187
188		if ((flags & CHECK_FOR_FAULT_STATS) != 0 &&
189				kcdata_type == STACKSHOT_KCTYPE_STACKSHOT_FAULT_STATS) {
190			memcpy(&fault_stats, kcdata_iter_payload(iter), sizeof(fault_stats));
191			found_fault_stats = true;
192		}
193
194		if (kcdata_type != KCDATA_TYPE_CONTAINER_BEGIN) {
195			continue;
196		}
197
198		if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_TASK) {
199			continue;
200		}
201
202		parsedContainer = parseKCDataContainer(&iter, &nserror);
203		T_ASSERT_NOTNULL(parsedContainer, "parsedContainer is not null");
204		T_ASSERT_NULL(nserror, "no NSError occured while parsing the kcdata container");
205
206		/*
207		 * given that we've targetted the pid, we can be sure that this
208		 * ts_pid will be the pid we expect
209		 */
210		parsedPid = parsedContainer[@"task_snapshots"][@"task_snapshot"][@"ts_pid"];
211		T_ASSERT_EQ([parsedPid intValue], target_pid, "found correct pid");
212
213		/* start parsing the threads */
214		parsedThreads = parsedContainer[@"task_snapshots"][@"thread_snapshots"];
215		for (id th_key in parsedThreads) {
216			uint32_t frame_index = 0;
217
218			if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) {
219				/* skip threads that don't have enough frames */
220				if ([parsedThreads[th_key][@"user_stack_frames"] count] < RECURSIONS) {
221					continue;
222				}
223
224				for (id frame in parsedThreads[th_key][@"user_stack_frames"]) {
225					if ((frame_index >= FIRST_RECURSIVE_FRAME) && (frame_index < (RECURSIONS - FIRST_RECURSIVE_FRAME))) {
226						if (expected_return_addr == 0ull) {
227							expected_return_addr = [frame[@"lr"] unsignedLongLongValue];
228						} else {
229							T_QUIET;
230							T_ASSERT_EQ(expected_return_addr, [frame[@"lr"] unsignedLongLongValue], "expected return address found");
231						}
232					}
233					frame_index ++;
234				}
235			} else {
236				T_ASSERT_NOTNULL(parsedThreads[th_key][@"kernel_stack_frames"],
237						"found kernel stack frames");
238			}
239
240		}
241	}
242
243	if (found_fault_stats) {
244		T_LOG("number of pages faulted in: %d", fault_stats.sfs_pages_faulted_in);
245		T_LOG("MATUs spent faulting: %lld", fault_stats.sfs_time_spent_faulting);
246		T_LOG("MATUS fault time limit: %lld", fault_stats.sfs_system_max_fault_time);
247		T_LOG("did we stop because of the limit?: %s", fault_stats.sfs_stopped_faulting ? "yes" : "no");
248		if (expected_return_addr != 0ull) {
249			T_ASSERT_GT(fault_stats.sfs_pages_faulted_in, 0, "faulted at least one page in");
250			T_LOG("NOTE: successfully faulted in the pages");
251		} else {
252			T_LOG("NOTE: We were not able to fault the stack's pages back in");
253
254			/* if we couldn't fault the pages back in, then at least verify that we tried */
255			T_ASSERT_GT(fault_stats.sfs_time_spent_faulting, 0ull, "spent time trying to fault");
256		}
257	} else if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) {
258		T_ASSERT_NE(expected_return_addr, 0ull, "found child thread with recursions");
259	}
260
261	if (flags & CHECK_FOR_FAULT_STATS) {
262		T_ASSERT_EQ(found_fault_stats, true, "found fault stats");
263	}
264
265	return ret;
266}
267
268void
269child_init(void)
270{
271#if !TARGET_OS_OSX
272	int freeze_state;
273#endif /* !TARGET_OS_OSX */
274	pid_t pid = getpid();
275	char padding[16 * 1024];
276	__asm__ volatile(""::"r"(padding));
277
278	T_LOG("child pid: %d\n", pid);
279
280#if !TARGET_OS_OSX
281	/* allow us to be frozen */
282	freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0);
283	if (freeze_state == 0) {
284		T_LOG("CHILD was found to be UNFREEZABLE, enabling freezing.");
285		memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, pid, 1, NULL, 0);
286		freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0);
287		T_ASSERT_EQ(freeze_state, 1, "successfully set freezeability");
288	}
289#else
290	T_LOG("Cannot change freezeability as freezing is only available on embedded devices");
291#endif /* !TARGET_OS_OSX */
292
293	/*
294	 * recurse a bunch of times to generate predictable data in the stackshot,
295	 * then send SIGUSR1 to the parent to let it know that we are done.
296	 */
297	child_recurse(RECURSIONS, 0, ^{
298		kill(getppid(), SIGUSR1);
299	});
300
301	T_ASSERT_FAIL("child_recurse returned, but it must not?");
302}
303
304void
305parent_helper_singleproc(int spin)
306{
307	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
308	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic_sp", NULL);
309	void *stackshot_config;
310
311	dispatch_async(dq, ^{
312		char padding[16 * 1024];
313		__asm__ volatile(""::"r"(padding));
314
315		child_recurse(RECURSIONS, spin, ^{
316			dispatch_semaphore_signal(child_done_sema);
317		});
318	});
319
320	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
321	T_LOG("done waiting for child");
322
323	/* take the stackshot and parse it */
324	stackshot_config = take_stackshot(getpid(), 0, 0);
325
326	/* check that the stackshot has the stack frames */
327	check_stackshot(stackshot_config, 0);
328
329	T_LOG("done!");
330}
331
332T_DECL(basic, "test that no-fault stackshot works correctly", T_META_TAG_VM_PREFERRED)
333{
334	char path[PATH_MAX];
335	uint32_t path_size = sizeof(path);
336	char *args[] = { path, "-n", "simple_child_process", NULL };
337	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic", NULL);
338	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
339	dispatch_source_t child_sig_src;
340	void *stackshot_config;
341
342	current_scenario_name = __func__;
343
344	T_LOG("parent pid: %d\n", getpid());
345	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
346
347	/* check if we can run the child successfully */
348#if !TARGET_OS_OSX
349	int freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
350	if (freeze_state == -1) {
351		T_SKIP("This device doesn't have CONFIG_FREEZE enabled.");
352	}
353#endif
354
355	/* setup signal handling */
356	signal(SIGUSR1, SIG_IGN);
357	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
358	dispatch_source_set_event_handler(child_sig_src, ^{
359		dispatch_semaphore_signal(child_done_sema);
360	});
361	dispatch_activate(child_sig_src);
362
363	/* create the child process */
364	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
365	T_ATEND(kill_children);
366
367	/* wait until the child has recursed enough */
368	dispatch_semaphore_wait(child_done_sema, dispatch_time(DISPATCH_TIME_NOW, 10 /*seconds*/ * 1000000000ULL));
369
370	T_LOG("child finished, parent executing");
371
372	/* take the stackshot and parse it */
373	stackshot_config = take_stackshot(child_pid, 0, 0);
374
375	/* check that the stackshot has the stack frames */
376	check_stackshot(stackshot_config, 0);
377
378	T_LOG("all done, killing child");
379
380	/* tell the child to quit */
381	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
382}
383
384T_DECL(basic_singleproc, "test that no-fault stackshot works correctly in single process setting", T_META_TAG_VM_PREFERRED)
385{
386	current_scenario_name = __func__;
387	parent_helper_singleproc(0);
388}
389
390T_DECL(basic_singleproc_spin, "test that no-fault stackshot works correctly in single process setting with spinning", T_META_TAG_VM_PREFERRED)
391{
392	current_scenario_name = __func__;
393	parent_helper_singleproc(1);
394}
395
396T_DECL(fault, "test that faulting stackshots work correctly", T_META_TAG_VM_PREFERRED)
397{
398	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_fault_accuracy", NULL);
399	dispatch_source_t child_sig_src;
400	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
401	void *stackshot_config;
402	int oldftm, newval = 1, freeze_enabled, oldratio, newratio = 0;
403	size_t oldlen = sizeof(oldftm), fe_len = sizeof(freeze_enabled), ratiolen = sizeof(oldratio);
404	char path[PATH_MAX];
405	uint32_t path_size = sizeof(path);
406	char *args[] = { path, "-n", "simple_child_process", NULL };
407
408	current_scenario_name = __func__;
409	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
410
411#if TARGET_OS_OSX
412	T_SKIP("freezing is not available on macOS");
413#endif /* TARGET_OS_OSX */
414
415	/* Try checking if freezing is enabled at all */
416	if (sysctlbyname("vm.freeze_enabled", &freeze_enabled, &fe_len, NULL, 0) == -1) {
417		if (errno == ENOENT) {
418			T_SKIP("This device doesn't have CONFIG_FREEZE enabled.");
419		} else {
420			T_FAIL("failed to query vm.freeze_enabled, errno: %d", errno);
421		}
422	}
423
424	if (!freeze_enabled) {
425		T_SKIP("Freeze is not enabled, skipping test.");
426	}
427
428	/* signal handling */
429	signal(SIGUSR1, SIG_IGN);
430	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
431	dispatch_source_set_event_handler(child_sig_src, ^{
432		dispatch_semaphore_signal(child_done_sema);
433	});
434	dispatch_activate(child_sig_src);
435
436	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
437	T_ATEND(kill_children);
438
439	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
440
441	/* keep processes in memory */
442	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", &oldftm, &oldlen, &newval, sizeof(newval)),
443			"disabled freezing to disk");
444
445	/* set the ratio to zero */
446	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", &oldratio, &ratiolen, &newratio, sizeof(newratio)), "disabled private:shared ratio checking");
447
448	/* freeze the child */
449	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze", NULL, 0, &child_pid, sizeof(child_pid)),
450			"froze child");
451
452	/* Sleep to allow the compressor to finish compressing the child */
453	sleep(5);
454
455	/* take the stackshot and parse it */
456	stackshot_config = take_stackshot(child_pid, STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0);
457
458	/* check that the stackshot has the stack frames */
459	check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS);
460
461	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", NULL, 0, &oldftm, sizeof(oldftm)),
462			"reset freezing to disk");
463
464	/* reset the private:shared ratio */
465	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", NULL, 0, &oldratio, sizeof(oldratio)), "reset private:shared ratio");
466
467	T_LOG("all done, killing child");
468
469	/* tell the child to quit */
470	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
471}
472
473T_DECL(fault_singleproc, "test that faulting stackshots work correctly in a single process setting", T_META_TAG_VM_PREFERRED)
474{
475	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
476	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.fault_sp", NULL);
477	void *stackshot_config;
478	__block pthread_t child_thread;
479	char *child_stack;
480	size_t child_stacklen;
481
482#if !TARGET_OS_OSX
483	T_SKIP("madvise(..., ..., MADV_PAGEOUT) is not available on embedded platforms");
484#endif /* !TARGET_OS_OSX */
485
486	dispatch_async(dq, ^{
487		char padding[16 * 1024];
488		__asm__ volatile(""::"r"(padding));
489
490		child_recurse(RECURSIONS, 0, ^{
491			child_thread = pthread_self();
492			dispatch_semaphore_signal(child_done_sema);
493		});
494	});
495
496	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
497	T_LOG("done waiting for child");
498
499	child_stack = pthread_get_stackaddr_np(child_thread);
500	child_stacklen = pthread_get_stacksize_np(child_thread);
501	child_stack -= child_stacklen;
502	T_LOG("child stack: [0x%p - 0x%p]: 0x%zu bytes", (void *)child_stack,
503			(void *)(child_stack + child_stacklen), child_stacklen);
504
505	/* paging out the child */
506	T_ASSERT_POSIX_SUCCESS(madvise(child_stack, child_stacklen, MADV_PAGEOUT), "paged out via madvise(2) the child stack");
507
508	/* take the stackshot and parse it */
509	stackshot_config = take_stackshot(getpid(), STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0);
510
511	/* check that the stackshot has the stack frames */
512	check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS);
513
514	T_LOG("done!");
515}
516
517T_DECL(zombie, "test that threads wedged in the kernel can be stackshot'd", T_META_TAG_VM_PREFERRED)
518{
519	dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.zombie", NULL);
520	dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0);
521	dispatch_source_t child_sig_src;
522	void *stackshot_config;
523	char path[PATH_MAX];
524	uint32_t path_size = sizeof(path);
525	char *args[] = { path, "-n", "sid_child_process", NULL };
526
527	current_scenario_name = __func__;
528	T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
529
530	T_LOG("parent pid: %d\n", getpid());
531
532	/* setup signal handling */
533	signal(SIGUSR1, SIG_IGN);
534	child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq);
535	dispatch_source_set_event_handler(child_sig_src, ^{
536		dispatch_semaphore_signal(child_done_sema);
537	});
538	dispatch_activate(child_sig_src);
539
540	/* create the child process */
541	T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched");
542	T_ATEND(kill_children);
543
544	/* wait until the child has recursed enough */
545	dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER);
546
547	T_LOG("child finished, parent executing. invoking jetsam");
548
549	T_ASSERT_POSIX_SUCCESS(memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0),
550			"jetsam'd the child");
551
552	/* Sleep to allow the target process to become zombified */
553	sleep(1);
554
555	/* take the stackshot and parse it */
556	stackshot_config = take_stackshot(child_pid, 0, 0);
557
558	/* check that the stackshot has the stack frames */
559	check_stackshot(stackshot_config, CHECK_FOR_KERNEL_THREADS);
560
561	T_LOG("all done, unwedging and killing child");
562
563	int v = 1;
564	T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.unwedge_thread", NULL, NULL, &v, sizeof(v)),
565			"unwedged child");
566
567	/* tell the child to quit */
568	T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child");
569}
570