diff --git a/usr.bin/ktrdump/ktrdump.c b/usr.bin/ktrdump/ktrdump.c index 1ba9831..25012ba 100644 --- a/usr.bin/ktrdump/ktrdump.c +++ b/usr.bin/ktrdump/ktrdump.c @@ -89,6 +89,25 @@ struct save_ctx { typedef void (*ktr_iter_cb_t)(void *, int, int, struct ktr_entry *, uint64_t *); +#ifdef __x86_64__ +/* defined according to the x86_64 ABI spec */ +struct my_va_list { + uint32_t gp_offset; /* offset to next available gpr in reg_save_area */ + uint32_t fp_offset; /* offset to next available fpr in reg_save_area */ + void *overflow_arg_area; /* args that are passed on the stack */ + struct reg_save_area *reg_save_area; /* register args */ +} __attribute__((packed)); + +typedef struct my_va_list machine_va_list; + +struct reg_save_area { + uint64_t rdi, rsi, rdx, rcx, r8, r9; + /* XMM registers follow, but we don't use them */ +}; +#elif __i386__ +typedef void *machine_va_list; +#endif + static int cflag; static int dflag; static int fflag; @@ -135,6 +154,7 @@ static void load_bufs(struct ktr_buffer *, struct ktr_entry **, int *); static void iterate_buf(FILE *, struct ktr_buffer *, int, u_int64_t *, ktr_iter_cb_t); static void iterate_bufs_timesorted(FILE *, struct ktr_buffer *, u_int64_t *, ktr_iter_cb_t); static void kvmfprintf(FILE *fp, const char *ctl, va_list va); +static int va_list_from_blob(machine_va_list *valist, const char *fmt, char *blob, size_t blobsize); /* * Reads the ktr trace buffer from kernel memory and prints the trace entries. @@ -472,14 +492,15 @@ print_entry(FILE *fo, int n, int row, struct ktr_entry *entry, if (info == NULL) info = kvm_ktrinfo(entry->ktr_info, &infoctx); if (info) { -#ifdef __amd64__ - /* XXX todo */ + machine_va_list ap; + const char *fmt; + fmt = kvm_string(info->kf_format, &fmtctx); + if (va_list_from_blob(&ap, fmt, + (char *)&entry->ktr_data, + info->kf_data_size)) + err(2, "Can't generate va_list from %s\n", fmt); kvmfprintf(fo, kvm_string(info->kf_format, &fmtctx), - (void *)&entry->ktr_data); -#else - kvmfprintf(fo, kvm_string(info->kf_format, &fmtctx), - (void *)&entry->ktr_data); -#endif + (void *)&ap); } } fprintf(fo, "\n"); @@ -1107,3 +1128,264 @@ usage(void) "[-N execfile] [-M corefile] [-o outfile]\n"); exit(1); } + +enum argument_class { + ARGCLASS_NONE, + ARGCLASS_INTEGER, + ARGCLASS_FP, + ARGCLASS_MEMORY, + ARGCLASS_ERR, +}; +static size_t +conversion_size(const char *fmt, enum argument_class *argclass) +{ + const char *p; + size_t convsize, intsz; + + *argclass = ARGCLASS_ERR; + if (fmt[0] != '%') + return -1; + + convsize = -1; + for (p = fmt + 1; p[0]; ++p) { + int again = 0; + /* + * Eat flags. Notice this will accept duplicate + * flags. + */ + switch (p[0]) { + case '#': + case '0': + case '-': + case ' ': + case '+': + case '\'': + again = !0; + break; + } + if (!again) + break; + } + /* Eat minimum field width, if any */ + for (; isdigit(p[0]); ++p) + ; + if (p[0] == '.') + ++p; + /* Eat precision, if any */ + for (; isdigit(p[0]); ++p) + ; + intsz = 0; + switch (p[0]) { + case 'h': + if (p[1] == 'h') { + ++p; + intsz = sizeof(char); + } else { + intsz = sizeof(short); + } + break; + case 'l': + if (p[1] == 'l') { + ++p; + intsz = sizeof(long long); + } else { + intsz = sizeof(long); + } + break; + case 'j': + intsz = sizeof(intmax_t); + break; + case 't': + intsz = sizeof(ptrdiff_t); + break; + case 'z': + intsz = sizeof(size_t); + break; + default: + p--; /* Anticipate the ++p that follows. Yes, I know. Eeek. */ + break; + } + if (intsz == 0) + intsz = sizeof(int); + ++p; + + switch (p[0]) { + case 'c': + /* for %c, we only store 1 byte in the ktr entry */ + convsize = sizeof(char); + *argclass = ARGCLASS_INTEGER; + break; + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + convsize = intsz; + *argclass = ARGCLASS_INTEGER; + break; + case 'p': + convsize = sizeof(void *); + *argclass = ARGCLASS_INTEGER; + break; + case 'f': + if (p[-1] == 'l') + convsize = sizeof(double); + else + convsize = sizeof(float); + break; + *argclass = ARGCLASS_FP; + case 's': + convsize = sizeof(char *); + *argclass = ARGCLASS_INTEGER; + break; + case '%': + convsize = 0; + *argclass = ARGCLASS_NONE; + break; + default: + fprintf(stderr, "Unknown conversion specifier %c " + "in fmt starting with %s", p[0], fmt - 1); + return -2; + } + return convsize; +} + +#ifdef __x86_64__ +static int +va_list_push_integral(struct my_va_list *valist, void *val, size_t valsize, + size_t *stacksize) +{ + uint64_t r; + + switch (valsize) { + case 1: + r = *(uint8_t *)val; break; + case 2: + r = *(uint32_t *)val; break; + + case 4: + r = (*(uint32_t *)val); break; + + case 8: + r = *(uint64_t *)val; break; + default: + err(1, "WTF\n"); + } + /* we always need to push the full 8 bytes */ + if ((valist->gp_offset + valsize) <= 48) { /* got a free reg */ + + memcpy(((char *)valist->reg_save_area + valist->gp_offset), + &r, sizeof(r)); + valist->gp_offset += sizeof(r); + return 0; + } + /* push to "stack" */ + if (!(valist->overflow_arg_area = realloc(valist->overflow_arg_area, + *stacksize + sizeof(r)))) + return -1; + memcpy((char *)valist->overflow_arg_area + *stacksize, &r, sizeof(r)); + *stacksize += sizeof(r); + return 0; +} + +static void +va_list_rewind(struct my_va_list *valist) +{ + valist->gp_offset = 0; +} + +static int +va_list_from_blob(machine_va_list *valist, const char *fmt, char *blob, size_t blobsize) +{ + struct reg_save_area *regs; + const char *f; + size_t sz; + + if (!(regs = malloc(sizeof(*regs)))) + return -1; + *valist = (struct my_va_list) { + .gp_offset = 0, + .fp_offset = 0, + .overflow_arg_area = NULL, + .reg_save_area = regs, + }; + enum argument_class argclass; + size_t stacksize = 0; + + for (f = fmt; *f != '\0'; ++f) { + if (*f != '%') + continue; + sz = conversion_size(f, &argclass); + if (argclass == ARGCLASS_INTEGER) { + if (blobsize < sz) { + fprintf(stderr, "not enough data available " + "for format: %s", fmt); + return -1; + } + if (va_list_push_integral(valist, blob, sz, &stacksize)) + return -1; + blob += sz; + blobsize -= sz; + } else if (argclass != ARGCLASS_NONE) + return -1; + /* walk past the '%' */ + ++f; + } + if (blobsize) { + fprintf(stderr, "Couldn't consume all data for format %s " + "(%zd bytes left over)\n", fmt, blobsize); + return -1; + } + va_list_rewind(valist); + return 0; +} +#elif __i386__ +static int +va_list_from_blob(machine_va_list *valist, const char *fmt, char *blob, size_t blobsize) +{ + const char *f; + char *n; + size_t bytes, sz; + enum argument_class argclass; + + n = NULL; + bytes = 0; + for (f = fmt; *f != '\0'; ++f) { + if (*f != '%') + continue; + sz = conversion_size(f, &argclass); + if (blobsize < sz) { + fprintf(stderr, "not enough data available " + "for format: %s", fmt); + return -1; + } + if ((argclass == ARGCLASS_INTEGER) && (sz < 4)) { + int i = -1; /* do C integer promotion */ + if (sz == 1) + i = *(char *)blob; + else + i = *(short *)blob; + if (!(n = realloc(n, bytes + 4))) + return -1; + memcpy(n + bytes, &i, sizeof(i)); + bytes += 4; + } else { + memcpy(n + bytes, blob, sz); + bytes += sz; + } + blob += sz; + blobsize -= sz; + + } + if (blobsize) { + fprintf(stderr, "Couldn't consume all data for format %s " + "(%zd bytes left over)\n", fmt, blobsize); + return -1; + } + *valist = n; + return 0; +} +#else +#error "Don't know how to get a va_list on this platform" +#endif