Index: sys/kern/kern_sysctl.c =================================================================== --- sys/kern/kern_sysctl.c +++ sys/kern/kern_sysctl.c @@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$"); #include "opt_capsicum.h" +#include "opt_ddb.h" #include "opt_ktrace.h" #include @@ -64,6 +65,12 @@ #include #endif +#ifdef DDB +#include +#include +#include +#endif + #include #include @@ -1609,9 +1616,10 @@ /* * A zero-length buffer indicates a fixed size read-only - * string: + * string. In ddb, don't worry about trying to make a malloced + * snapshot. */ - if (arg2 == 0) { + if (arg2 == 0 || kdb_active) { arg2 = strlen((char *)arg1) + 1; ro_string = 1; } @@ -2256,3 +2264,528 @@ sbuf_set_drain(s, sbuf_sysctl_drain, req); return (s); } + +#ifdef DDB + +/* The current OID the debugger is working with */ +static struct sysctl_oid *g_ddb_oid; + +/* The current flags specified by the user */ +static int g_ddb_sysctl_flags; + +/* Check to see if the last sysctl printed */ +static int g_ddb_sysctl_printed; + +static const int ctl_sign[CTLTYPE+1] = { + [CTLTYPE_INT] = 1, + [CTLTYPE_LONG] = 1, + [CTLTYPE_S8] = 1, + [CTLTYPE_S16] = 1, + [CTLTYPE_S32] = 1, + [CTLTYPE_S64] = 1, +}; + +static const int ctl_size[CTLTYPE+1] = { + [CTLTYPE_INT] = sizeof(int), + [CTLTYPE_UINT] = sizeof(u_int), + [CTLTYPE_LONG] = sizeof(long), + [CTLTYPE_ULONG] = sizeof(u_long), + [CTLTYPE_S8] = sizeof(int8_t), + [CTLTYPE_S16] = sizeof(int16_t), + [CTLTYPE_S32] = sizeof(int32_t), + [CTLTYPE_S64] = sizeof(int64_t), + [CTLTYPE_U8] = sizeof(uint8_t), + [CTLTYPE_U16] = sizeof(uint16_t), + [CTLTYPE_U32] = sizeof(uint32_t), + [CTLTYPE_U64] = sizeof(uint64_t), +}; + +#define DB_SYSCTL_NAME_ONLY 0x001 /* Compare with -N */ +#define DB_SYSCTL_VALUE_ONLY 0x002 /* Compare with -n */ +#define DB_SYSCTL_OPAQUE 0x004 /* Compare with -o */ +#define DB_SYSCTL_HEX 0x008 /* Compare with -x */ + +#define DB_SYSCTL_SAFE_ONLY 0x100 /* Only simple types */ + +static const char db_sysctl_modifs[] = { + 'N', 'n', 'o', 'x', +}; + +static const int db_sysctl_modif_values[] = { + DB_SYSCTL_NAME_ONLY, DB_SYSCTL_VALUE_ONLY, + DB_SYSCTL_OPAQUE, DB_SYSCTL_HEX, +}; + +/* Handlers considered safe to print while recursing */ +static int (* const db_safe_handlers[])(SYSCTL_HANDLER_ARGS) = { + sysctl_handle_bool, + sysctl_handle_8, + sysctl_handle_16, + sysctl_handle_32, + sysctl_handle_64, + sysctl_handle_int, + sysctl_handle_long, + sysctl_handle_string, + sysctl_handle_opaque, +}; + +/* + * Use in place of sysctl_old_kernel to print sysctl values. + * + * Compare to the output handling in show_var from sbin/sysctl/sysctl.c + */ +static int +sysctl_old_ddb(struct sysctl_req *req, const void *ptr, size_t len) +{ + const u_char *val, *p; + const char *sep1; + size_t intlen, slen; + uintmax_t umv; + intmax_t mv; + int sign, ctltype, hexlen, xflag, error; + + /* Suppress false-positive GCC uninitialized variable warnings */ + mv = 0; + umv = 0; + + slen = len; + val = p = ptr; + + if (ptr == NULL) { + error = 0; + goto out; + } + + /* We are going to print */ + g_ddb_sysctl_printed = 1; + + xflag = g_ddb_sysctl_flags & DB_SYSCTL_HEX; + + ctltype = (g_ddb_oid->oid_kind & CTLTYPE); + sign = ctl_sign[ctltype]; + intlen = ctl_size[ctltype]; + + switch (ctltype) { + case CTLTYPE_NODE: + case CTLTYPE_STRING: + db_printf("%.*s", (int) len, (const char *) p); + error = 0; + goto out; + + case CTLTYPE_INT: + case CTLTYPE_UINT: + case CTLTYPE_LONG: + case CTLTYPE_ULONG: + case CTLTYPE_S8: + case CTLTYPE_S16: + case CTLTYPE_S32: + case CTLTYPE_S64: + case CTLTYPE_U8: + case CTLTYPE_U16: + case CTLTYPE_U32: + case CTLTYPE_U64: + hexlen = 2 + (intlen * CHAR_BIT + 3) / 4; + sep1 = ""; + while (len >= intlen) { + switch (ctltype) { + case CTLTYPE_INT: + case CTLTYPE_UINT: + umv = *(const u_int *)p; + mv = *(const int *)p; + break; + case CTLTYPE_LONG: + case CTLTYPE_ULONG: + umv = *(const u_long *)p; + mv = *(const long *)p; + break; + case CTLTYPE_S8: + case CTLTYPE_U8: + umv = *(const uint8_t *)p; + mv = *(const int8_t *)p; + break; + case CTLTYPE_S16: + case CTLTYPE_U16: + umv = *(const uint16_t *)p; + mv = *(const int16_t *)p; + break; + case CTLTYPE_S32: + case CTLTYPE_U32: + umv = *(const uint32_t *)p; + mv = *(const int32_t *)p; + break; + case CTLTYPE_S64: + case CTLTYPE_U64: + umv = *(const uint64_t *)p; + mv = *(const int64_t *)p; + break; + } + + db_printf("%s", sep1); + if (xflag) + db_printf("%#0*jx", hexlen, umv); + else if (!sign) + db_printf("%ju", umv); + else if (g_ddb_oid->oid_fmt[1] == 'K') { + /* Kelvins are currently unsupported. */ + error = EOPNOTSUPP; + goto out; + } else + db_printf("%jd", mv); + + sep1 = " "; + len -= intlen; + p += intlen; + } + error = 0; + goto out; + + case CTLTYPE_OPAQUE: + /* TODO: Support struct functions. */ + + /* FALLTHROUGH */ + default: + db_printf("Format:%s Length:%zu Dump:0x", + g_ddb_oid->oid_fmt, len); + while (len-- && (xflag || p < val + 16)) + db_printf("%02x", *p++); + if (!xflag && len > 16) + db_printf("..."); + error = 0; + goto out; + } + +out: + req->oldidx += slen; + return (error); +} + +/* + * Avoid setting new sysctl values from the debugger + */ +static int +sysctl_new_ddb(struct sysctl_req *req, void *p, size_t l) +{ + + if (!req->newptr) + return (0); + + /* Changing sysctls from the debugger is currently unsupported */ + return (EPERM); +} + +/* + * Run a sysctl handler with the DDB oldfunc and newfunc attached. + * Instead of copying any output to a buffer we'll dump it right to + * the console. + */ +static int +db_sysctl(struct sysctl_oid *oidp, int *name, u_int namelen, + void *old, size_t *oldlenp, size_t *retval, int flags) +{ + struct sysctl_req req; + int error; + + /* Setup the request */ + bzero(&req, sizeof req); + req.td = kdb_thread; + req.oldfunc = sysctl_old_ddb; + req.newfunc = sysctl_new_ddb; + req.lock = REQ_UNWIRED; + if (oldlenp) { + req.oldlen = *oldlenp; + } + req.validlen = req.oldlen; + if (old) { + req.oldptr = old; + } + + /* Setup our globals for sysctl_old_ddb */ + g_ddb_oid = oidp; + g_ddb_sysctl_flags = flags; + g_ddb_sysctl_printed = 0; + + error = sysctl_root(0, name, namelen, &req); + + /* Reset globals */ + g_ddb_oid = NULL; + g_ddb_sysctl_flags = 0; + + if (retval) { + if (req.oldptr && req.oldidx > req.validlen) + *retval = req.validlen; + else + *retval = req.oldidx; + } + return (error); +} + +/* + * Show a sysctl's name + */ +static void +db_show_oid_name(int *oid, size_t nlen) +{ + struct sysctl_oid *oidp; + int qoid[CTL_MAXNAME+2]; + int error; + + qoid[0] = 0; + memcpy(qoid + 2, oid, nlen * sizeof(int)); + qoid[1] = 1; + + error = sysctl_find_oid(qoid, nlen + 2, &oidp, NULL, NULL); + if (error) + db_error("sysctl name oid"); + + error = db_sysctl(oidp, qoid, nlen + 2, NULL, NULL, NULL, 0); + if (error) + db_error("sysctl name"); +} + +/* + * Check to see if an OID is safe to print from ddb. + */ +static bool +db_oid_safe(const struct sysctl_oid *oidp) +{ + for (unsigned int i = 0; i < nitems(db_safe_handlers); ++i) { + if (oidp->oid_handler == db_safe_handlers[i]) + return (true); + } + + return (false); +} + +/* + * Show a sysctl at a specific OID + * Compare to the input handling in show_var from sbin/sysctl/sysctl.c + */ +static int +db_show_oid(struct sysctl_oid *oidp, int *oid, size_t nlen, int flags) +{ + int error, xflag, oflag, Nflag, nflag; + size_t len; + + xflag = flags & DB_SYSCTL_HEX; + oflag = flags & DB_SYSCTL_OPAQUE; + nflag = flags & DB_SYSCTL_VALUE_ONLY; + Nflag = flags & DB_SYSCTL_NAME_ONLY; + + if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_OPAQUE && + (!xflag && !oflag)) + return (0); + + if (Nflag) { + db_show_oid_name(oid, nlen); + error = 0; + goto out; + } + + if (!nflag) { + db_show_oid_name(oid, nlen); + db_printf(": "); + } + + if ((flags & DB_SYSCTL_SAFE_ONLY) && !db_oid_safe(oidp)) { + db_printf("Skipping, unsafe to print while recursing."); + error = 0; + goto out; + } + + /* Try once, and ask about the size */ + len = 0; + error = db_sysctl(oidp, oid, nlen, + NULL, NULL, &len, flags); + if (error) + goto out; + + if (!g_ddb_sysctl_printed) + /* Lie about the size */ + error = db_sysctl(oidp, oid, nlen, + (void *) 1, &len, NULL, flags); + +out: + db_printf("\n"); + return (error); +} + +/* + * Show all sysctls under a specific OID + * Compare to sysctl_all from sbin/sysctl/sysctl.c + */ +static int +db_show_sysctl_all(int *oid, size_t len, int flags) +{ + struct sysctl_oid *oidp; + int name1[CTL_MAXNAME + 2], name2[CTL_MAXNAME + 2]; + size_t l1, l2; + + name1[0] = 0; + name1[1] = 2; + l1 = 2; + if (len) { + memcpy(name1+2, oid, len * sizeof(int)); + l1 +=len; + } else { + name1[2] = 1; + l1++; + } + for (;;) { + int i, error; + + l2 = sizeof(name2); + error = kernel_sysctl(kdb_thread, name1, l1, + name2, &l2, NULL, 0, &l2, 0); + if (error != 0) { + if (error == ENOENT) + return (0); + else + db_error("sysctl(getnext)"); + } + + l2 /= sizeof(int); + + if (l2 < (unsigned int)len) + return (0); + + for (i = 0; i < len; i++) + if (name2[i] != oid[i]) + return (0); + + /* Find the OID in question */ + error = sysctl_find_oid(name2, l2, &oidp, NULL, NULL); + if (error) + return (error); + + i = db_show_oid(oidp, name2, l2, flags | DB_SYSCTL_SAFE_ONLY); + + if (db_pager_quit) + return (0); + + memcpy(name1+2, name2, l2 * sizeof(int)); + l1 = 2 + l2; + } +} + +/* + * Show a sysctl by its user facing string + */ +static int +db_sysctlbyname(char *name, int flags) +{ + struct sysctl_oid *oidp; + int oid[CTL_MAXNAME]; + int error, nlen; + + error = name2oid(name, oid, &nlen, &oidp); + if (error) { + return (error); + } + + if ((oidp->oid_kind & CTLTYPE) == CTLTYPE_NODE) { + db_show_sysctl_all(oid, nlen, flags); + } else { + error = db_show_oid(oidp, oid, nlen, flags); + } + + return (error); +} + +static void +db_sysctl_cmd_usage(void) +{ + db_printf( + " sysctl [/Nnox] \n" + " \n" + " The name of the sysctl to show. \n" + " \n" + " Show a sysctl by hooking into SYSCTL_IN and SYSCTL_OUT. \n" + " This will work for most sysctls, but should not be used \n" + " with sysctls that are known to malloc. \n" + " \n" + " While recursing any \"unsafe\" sysctls will be skipped. \n" + " Call sysctl directly on the sysctl to try printing the \n" + " skipped sysctl. This is unsafe and may make the ddb \n" + " session unusable. \n" + " \n" + " Arguments: \n" + " /N Display only the name of the sysctl. \n" + " /n Display only the value of the sysctl. \n" + " /o Display opaque values. \n" + " /x Display the sysctl in hex. \n" + " \n" + "For example: \n" + "sysctl vm.v_free_min \n" + "vn.v_free_min: 12669 \n" + ); +} + +/* + * Show a specific sysctl similar to sysctl (8). + */ +DB_FUNC(sysctl, db_sysctl_cmd, db_cmd_table, CS_OWN, NULL) +{ + char name[TOK_STRING_SIZE]; + int error, i, t, flags; + + /* Parse the modifiers */ + t = db_read_token(); + if (t == tSLASH || t == tMINUS) { + t = db_read_token(); + if (t != tIDENT) { + db_printf("Bad modifier\n"); + error = EINVAL; + goto out; + } + db_strcpy(modif, db_tok_string); + } + else { + db_unread_token(t); + modif[0] = '\0'; + } + + flags = 0; + for (i = 0; i < nitems(db_sysctl_modifs); i++) { + if (strchr(modif, db_sysctl_modifs[i])) { + flags |= db_sysctl_modif_values[i]; + } + } + + /* Parse the sysctl names */ + t = db_read_token(); + if (t != tIDENT) { + db_printf("Need sysctl name\n"); + error = EINVAL; + goto out; + } + + /* Copy the name into a temporary buffer */ + db_strcpy(name, db_tok_string); + + /* Ensure there is no trailing cruft */ + t = db_read_token(); + if (t != tEOL) { + db_printf("Unexpected sysctl argument\n"); + error = EINVAL; + goto out; + } + + error = db_sysctlbyname(name, flags); + if (error == ENOENT) { + db_printf("unknown oid: '%s'\n", db_tok_string); + goto out; + } else if (error) { + db_printf("%s: error: %d\n", db_tok_string, error); + goto out; + } + +out: + /* Ensure we eat all of our text */ + db_flush_lex(); + + if (error == EINVAL) { + db_sysctl_cmd_usage(); + } +} + +#endif /* DDB */