Module Name: src Committed By: pgoyette Date: Sun Jan 1 23:58:47 UTC 2017
Modified Files: src/sys/kern: kern_history.c src/sys/sys: kernhist.h Log Message: Provide a sysctl method of exporting the kernel history data. XXX vmstat will be update soon to use the sysctl rather than grovelling XXX through kvm. To generate a diff of this commit: cvs rdiff -u -r1.8 -r1.9 src/sys/kern/kern_history.c cvs rdiff -u -r1.13 -r1.14 src/sys/sys/kernhist.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/kern/kern_history.c diff -u src/sys/kern/kern_history.c:1.8 src/sys/kern/kern_history.c:1.9 --- src/sys/kern/kern_history.c:1.8 Mon Dec 26 23:49:53 2016 +++ src/sys/kern/kern_history.c Sun Jan 1 23:58:47 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: kern_history.c,v 1.8 2016/12/26 23:49:53 pgoyette Exp $ */ +/* $NetBSD: kern_history.c,v 1.9 2017/01/01 23:58:47 pgoyette Exp $ */ /* * Copyright (c) 1997 Charles D. Cranor and Washington University. @@ -33,7 +33,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: kern_history.c,v 1.8 2016/12/26 23:49:53 pgoyette Exp $"); +__KERNEL_RCSID(0, "$NetBSD: kern_history.c,v 1.9 2017/01/01 23:58:47 pgoyette Exp $"); #include "opt_ddb.h" #include "opt_kernhist.h" @@ -41,11 +41,14 @@ __KERNEL_RCSID(0, "$NetBSD: kern_history #include "opt_usb.h" #include "opt_uvmhist.h" #include "opt_biohist.h" +#include "opt_sysctl.h" #include <sys/param.h> #include <sys/systm.h> #include <sys/cpu.h> +#include <sys/sysctl.h> #include <sys/kernhist.h> +#include <sys/kmem.h> #ifdef UVMHIST #include <uvm/uvm.h> @@ -63,6 +66,12 @@ __KERNEL_RCSID(0, "$NetBSD: kern_history KERNHIST_DECL(scdebughist); #endif +struct addr_xlt { + const char *addr; + size_t len; + uint32_t offset; +}; + /* * globals */ @@ -71,6 +80,8 @@ struct kern_history_head kern_histories; int kernhist_print_enabled = 1; +int sysctl_hist_node; + #ifdef DDB /* @@ -79,10 +90,11 @@ int kernhist_print_enabled = 1; void kernhist_dump(struct kern_history *, void (*)(const char *, ...) __printflike(1, 2)); -void kernhist_dumpmask(u_int32_t); +void kernhist_dumpmask(uint32_t); static void kernhist_dump_histories(struct kern_history *[], void (*)(const char *, ...) __printflike(1, 2)); +static int sysctl_kernhist_helper(SYSCTLFN_PROTO); /* * call this from ddb @@ -176,7 +188,7 @@ restart: * expects the system to be quiesced, no locking */ void -kernhist_dumpmask(u_int32_t bitmask) /* XXX only support 32 hists */ +kernhist_dumpmask(uint32_t bitmask) /* XXX only support 32 hists */ { struct kern_history *hists[MAXHISTS + 1]; int i = 0; @@ -256,3 +268,251 @@ kernhist_print(void *addr, void (*pr)(co } #endif + +/* + * sysctl interface + */ + +/* + * sysctl_hist_new() + * + * Scan the list of histories; for any history that does not already + * have a sysctl node (under kern.hist) we create a new one and record + * it's node number. + */ +static void +sysctl_hist_new(void) +{ + int error; + struct kern_history *h; + const struct sysctlnode *rnode = NULL; + + LIST_FOREACH(h, &kern_histories, list) { + if (h->s != 0) + continue; + error = sysctl_createv(NULL, 0, NULL, &rnode, + CTLFLAG_PERMANENT, + CTLTYPE_STRUCT, h->name, + SYSCTL_DESCR("history data"), + sysctl_kernhist_helper, 0, NULL, 0, + CTL_KERN, sysctl_hist_node, CTL_CREATE, CTL_EOL); + if (error == 0) + h->s = rnode->sysctl_num; + } +} + +/* + * sysctl_kerhnist_init() + * + * Create the 2nd level "hw.hist" sysctl node + */ +void +sysctl_kernhist_init(void) +{ + const struct sysctlnode *rnode = NULL; + + sysctl_createv(NULL, 0, NULL, &rnode, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "hist", + SYSCTL_DESCR("kernel history tables"), + sysctl_kernhist_helper, 0, NULL, 0, + CTL_KERN, CTL_CREATE, CTL_EOL); + sysctl_hist_node = rnode->sysctl_num; + + sysctl_hist_new(); +} + +/* + * find_string() + * + * Search the address-to-offset translation table for matching an + * address and len, and return the index of the entry we found. If + * not found, returns index 0 which points to the "?" entry. (We + * start matching at index 1, ignoring any matches of the "?" entry + * itself.) + */ +static int +find_string(struct addr_xlt table[], size_t *count, const char *string, + size_t len) +{ + int i; + + for (i = 1; i < *count; i++) + if (string == table[i].addr && len == table[i].len) + return i; + + return 0; +} + +/* + * add_string() + * + * If the string and len are unique, add a new address-to-offset + * entry in the translation table and set the offset of the next + * entry. + */ +static void +add_string(struct addr_xlt table[], size_t *count, const char *string, + size_t len) +{ + + if (find_string(table, count, string, len) == 0) { + table[*count].addr = string; + table[*count].len = len; + table[*count + 1].offset = table[*count].offset + len + 1; + (*count)++; + } +} + +/* + * sysctl_kernhist_helper + * + * This helper routine is called for all accesses to the kern.hist + * hierarchy. + */ +static int +sysctl_kernhist_helper(SYSCTLFN_ARGS) +{ + struct kern_history *h; + struct kern_history_ent *in_evt; + struct sysctl_history_event *out_evt; + struct sysctl_history *buf; + struct addr_xlt *xlate_t, *xlt; + size_t bufsize, xlate_s; + size_t xlate_c; + const char *strp; + char *next; + int i, j; + int error; + + sysctl_hist_new(); /* make sure we're up to date */ + + if (namelen == 1 && name[0] == CTL_QUERY) + return sysctl_query(SYSCTLFN_CALL(rnode)); + + /* + * Disallow userland updates, verify that we arrived at a + * valid history rnode + */ + if (newp) + return EPERM; + if (namelen != 1 || name[0] != CTL_EOL) + return EINVAL; + + /* Find the correct kernhist for this sysctl node */ + LIST_FOREACH(h, &kern_histories, list) { + if (h->s == rnode->sysctl_num) + break; + } + if (h == NULL) + return ENOENT; + + /* + * Worst case is two string pointers per history entry, plus + * two for the history name and "?" string; allocate an extra + * entry since we pre-set the "next" entry's offset member. + */ + xlate_s = sizeof(struct addr_xlt) * h->n * 2 + 3; + xlate_t = kmem_alloc(xlate_s, KM_SLEEP); + xlate_c = 0; + + /* offset 0 reserved for NULL pointer, ie unused history entry */ + xlate_t[0].offset = 1; + + /* + * If the history gets updated and an unexpected string is + * found later, we'll point it here. Otherwise, we'd have to + * repeat this process iteratively, and it could take multiple + * iterations before terminating. + */ + add_string(xlate_t, &xlate_c, "?", 0); + + /* Copy the history name itself to the export structure */ + add_string(xlate_t, &xlate_c, h->name, h->namelen); + + /* + * Loop through all used history entries to find the unique + * fn and fmt strings + */ + for (i = 0, in_evt = h->e; i < h->n; i++, in_evt++) { + if (in_evt->fn == NULL) + continue; + add_string(xlate_t, &xlate_c, in_evt->fn, in_evt->fnlen); + add_string(xlate_t, &xlate_c, in_evt->fmt, in_evt->fmtlen); + } + + /* Total buffer size includes header, events, and string table */ + bufsize = sizeof(struct sysctl_history) + + h->n * sizeof(struct sysctl_history_event) + + xlate_t[xlate_c].offset; + buf = kmem_alloc(bufsize, KM_SLEEP); + + /* + * Copy history header info to the export structure + */ + j = find_string(xlate_t, &xlate_c, h->name, h->namelen); + buf->sh_listentry.shle_nameoffset = xlate_t[j].offset; + buf->sh_listentry.shle_numentries = h->n; + buf->sh_listentry.shle_nextfree = h->f; + + /* + * Loop through the history events again, copying the data to + * the export structure + */ + for (i = 0, in_evt = h->e, out_evt = buf->sh_events; i < h->n; + i++, in_evt++, out_evt++) { + if (in_evt->fn == NULL) { /* skip unused entries */ + out_evt->she_funcoffset = 0; + out_evt->she_fmtoffset = 0; + continue; + } + TIMEVAL_TO_TIMESPEC(&in_evt->tv, &out_evt->she_tspec); + out_evt->she_callnumber = in_evt->call; + out_evt->she_cpunum = in_evt->cpunum; + out_evt->she_values[0] = in_evt->v[0]; + out_evt->she_values[1] = in_evt->v[1]; + out_evt->she_values[2] = in_evt->v[2]; + out_evt->she_values[3] = in_evt->v[3]; + j = find_string(xlate_t, &xlate_c, in_evt->fn, in_evt->fnlen); + out_evt->she_funcoffset = xlate_t[j].offset; + j = find_string(xlate_t, &xlate_c, in_evt->fmt, in_evt->fmtlen); + out_evt->she_fmtoffset = xlate_t[j].offset; + } + + /* + * Finally, fill the text string area with all the unique + * strings we found earlier. + * + * Skip the initial byte, since we use an offset of 0 to mean + * a NULL pointer (which means an unused history event). + */ + strp = next = (char *)(&buf->sh_events[h->n]); + *next++ = '\0'; + + /* + * Then copy each string into the export structure, making + * sure to terminate each string with a '\0' character + */ + for (i = 0, xlt = xlate_t; i < xlate_c; i++, xlt++) { + KASSERTMSG((next - strp) == xlt->offset, + "entry %d at wrong offset %"PRIu32, i, xlt->offset); + memcpy(next, xlt->addr, xlt->len); + next += xlt->len; + *next++ = '\0'; + } + + /* Copy data to userland */ + error = copyout(buf, oldp, min(bufsize, *oldlenp)); + + /* If copyout was successful but only partial, report ENOMEM */ + if (error == 0 && *oldlenp < bufsize) + error = ENOMEM; + + *oldlenp = bufsize; /* inform userland of space requirements */ + + /* Free up the stuff we allocated */ + kmem_free(buf, bufsize); + kmem_free(xlate_t, xlate_s); + + return error; +} Index: src/sys/sys/kernhist.h diff -u src/sys/sys/kernhist.h:1.13 src/sys/sys/kernhist.h:1.14 --- src/sys/sys/kernhist.h:1.13 Mon Dec 26 23:12:33 2016 +++ src/sys/sys/kernhist.h Sun Jan 1 23:58:47 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: kernhist.h,v 1.13 2016/12/26 23:12:33 pgoyette Exp $ */ +/* $NetBSD: kernhist.h,v 1.14 2017/01/01 23:58:47 pgoyette Exp $ */ /* * Copyright (c) 1997 Charles D. Cranor and Washington University. @@ -63,6 +63,39 @@ struct kern_history { unsigned int n; /* number of entries */ unsigned int f; /* next free one */ struct kern_history_ent *e; /* the allocated entries */ + int s; /* our sysctl number */ +}; + +/* + * structs for exporting history info via sysctl(3) + */ + +/* info for a single kernhist */ +struct sysctl_history_list_entry { + uint32_t shle_nameoffset; + uint32_t shle_numentries; + uint32_t shle_nextfree; + uint32_t shle_filler; +}; + +/* info for a single history event */ +struct sysctl_history_event { + struct timespec she_tspec; + uint64_t she_callnumber; + uint64_t she_values[4]; + uint32_t she_cpunum; + uint32_t she_fmtoffset; + uint32_t she_funcoffset; + uint32_t she_filler; +}; + +/* list of all events for a single history */ +struct sysctl_history { + struct sysctl_history_list_entry + sh_listentry; + struct sysctl_history_event + sh_events[]; + /* char sh_strings[]; */ /* follows last sh_events */ }; LIST_HEAD(kern_history_head, kern_history); @@ -123,6 +156,7 @@ do { \ (NAME).f = 0; \ (NAME).e = (struct kern_history_ent *) \ kmem_zalloc(sizeof(struct kern_history_ent) * (N), KM_SLEEP); \ + (NAME).s = 0; \ LIST_INSERT_HEAD(&kern_histories, &(NAME), list); \ } while (/*CONSTCOND*/ 0) @@ -133,6 +167,7 @@ do { \ .n = sizeof(BUF) / sizeof(struct kern_history_ent), \ .f = 0, \ .e = (struct kern_history_ent *) (BUF), \ + .s = 0, \ /* BUF will inititalized to zeroes by being in .bss */ \ } @@ -146,6 +181,7 @@ do { \ (NAME).n = sizeof(BUF) / sizeof(struct kern_history_ent); \ (NAME).f = 0; \ (NAME).e = (struct kern_history_ent *) (BUF); \ + (NAME).s = 0; \ memset((NAME).e, 0, sizeof(struct kern_history_ent) * (NAME).n); \ KERNHIST_LINK_STATIC(NAME); \ } while (/*CONSTCOND*/ 0) @@ -232,6 +268,8 @@ void kernhist_dump(struct kern_history * void kernhist_print(void *, void (*)(const char *, ...) __printflike(1, 2)); #endif /* DDB */ +void sysctl_kernhist_init(void); + #endif /* KERNHIST */ #endif /* _KERNEL */