DF-2815 / churn.c
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 | /* * DF-2815 - kern_shutdown.c dumper state race churner (no in-loop I/O). * * Spams DIOCGKERNELDUMP (set/clear kernel dump device) against a disk * slice with no synchronization against dumpsys(). set_dumper() * (sys/kern/kern_shutdown.c:948) does an unlocked EBUSY check-then-write * of the global `dumper' struct; set_dumper(NULL) bzeros it. dumpsys() * (kern_shutdown.c:980) checks dumper.dumper then hands &dumper (the * global itself, not a snapshot) to md_dumpsys(), which re-reads * di->priv on every dump write (minidump_machdep.c:95/143/348/427). * * A clear landing after dumpsys() has started the dump leaves * di->priv == NULL -> dev_ddump(NULL,...) -> dev_needmplock() * (kern_device.c:119) dereferences NULL->si_ops -> kernel page fault * while dumping. * * IMPORTANT: no file/console writes inside the churn loop -- the root * filesystem is force-unmounted seconds before the dump and any write * would block the churn threads forever (observed with an earlier * heartbeat-logging variant: churn stops at unmount time and the dump * completes cleanly). * * modes: * 0 - clear only * 1 - alternate set/clear every ioctl * 3 - mostly set, clear every CLEARMASK ioctls (dump starts, then dies) * * usage: churn <device> <seconds> <mode> <nthreads> */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/diskslice.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #define CLEARMASK 0x3F /* clear every 64th ioctl in mode 3 */ static int fd; static int confd = -1; static int mode = 3; static volatile unsigned long n_ok, n_err, n_set, n_clr; static volatile int stop; static void * churn(void *x __unused) { u_int u; unsigned long i = 0; static const char marker[] = "c"; while (!stop) { switch (mode) { case 0: u = 0; break; case 1: u = (i & 1) ? 0 : 1; break; case 3: default: u = ((i & CLEARMASK) == 0) ? 0 : 1; break; } if (ioctl(fd, DIOCGKERNELDUMP, &u) == 0) { n_ok++; if (u) n_set++; else n_clr++; } else { n_err++; } i++; } return (NULL); } static void * ticker(void *x __unused) { /* no-op: console markers contaminate the experiment once the console * enters polled mode during dumpsys; liveness is instead proven by * the kproc-wait phase markers in the diagnostic variant */ while (!stop) usleep(1000000); return (NULL); } int main(int argc, char **argv) { pthread_t tid[64], ttid; int nthreads = 2; int secs = 60; int i; if (argc < 2) { fprintf(stderr, "usage: %s <device> [secs] [mode] [nthreads]\n", argv[0]); exit(1); } if (argc > 2) secs = atoi(argv[2]); if (argc > 3) mode = atoi(argv[3]); if (argc > 4) nthreads = atoi(argv[4]); if (nthreads > 64) nthreads = 64; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror(argv[1]); exit(1); } confd = open("/dev/console", O_WRONLY | O_NONBLOCK); pthread_create(&ttid, NULL, ticker, NULL); for (i = 0; i < nthreads; i++) pthread_create(&tid[i], NULL, churn, NULL); sleep(secs); stop = 1; for (i = 0; i < nthreads; i++) pthread_join(tid[i], NULL); printf("churn: mode=%d ok=%lu err=%lu set=%lu clr=%lu\n", mode, n_ok, n_err, n_set, n_clr); return (0); } |