This patch changes libc's _mcleanup() function so it serializes gmon profiling data with utrace(2). gprof(1) is also modified: it can now deserialize that profiling data from a ktrace(2) file.
Only apply this patch if you are testing the profclock() patch. Index: lib/libc/gmon/gmon.c =================================================================== RCS file: /cvs/src/lib/libc/gmon/gmon.c,v retrieving revision 1.33 diff -u -p -r1.33 gmon.c --- lib/libc/gmon/gmon.c 26 Jul 2022 04:07:13 -0000 1.33 +++ lib/libc/gmon/gmon.c 19 Jun 2023 03:15:14 -0000 @@ -28,16 +28,22 @@ * SUCH DAMAGE. */ -#include <sys/time.h> +#include <sys/types.h> #include <sys/gmon.h> +#include <sys/ktrace.h> #include <sys/mman.h> #include <sys/sysctl.h> +#include <sys/time.h> +#include <assert.h> +#include <err.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <limits.h> +#include <stdarg.h> #include <unistd.h> struct gmonparam _gmonparam = { GMON_PROF_OFF }; @@ -47,6 +53,7 @@ static int s_scale; #define SCALE_1_TO_1 0x10000L #define ERR(s) write(STDERR_FILENO, s, sizeof(s)) +#define GMON_LABEL "_openbsd_libc_gmon" PROTO_NORMAL(moncontrol); PROTO_DEPRECATED(monstartup); @@ -136,24 +143,17 @@ __strong_alias(_monstartup,monstartup); void _mcleanup(void) { - int fd; - int fromindex; + char ubuf[KTR_USER_MAXLEN + 1]; /* +1 for NUL, for snprintf(3) */ + int error, fromindex, len; int endfrom; - u_long frompc; + u_long frompc, i, j, limit; int toindex; struct rawarc rawarc; struct gmonparam *p = &_gmonparam; struct gmonhdr gmonhdr, *hdr; struct clockinfo clockinfo; const int mib[2] = { CTL_KERN, KERN_CLOCKRATE }; - size_t size; - char *profdir; - char *proffile; - char buf[PATH_MAX]; -#ifdef DEBUG - int log, len; - char dbuf[200]; -#endif + size_t off, sample_limit, sample_total, size; if (p->state == GMON_PROF_ERROR) ERR("_mcleanup: tos overflow\n"); @@ -171,66 +171,7 @@ _mcleanup(void) moncontrol(0); - if (issetugid() == 0 && (profdir = getenv("PROFDIR")) != NULL) { - char *s, *t, *limit; - pid_t pid; - long divisor; - - /* If PROFDIR contains a null value, no profiling - output is produced */ - if (*profdir == '\0') { - return; - } - - limit = buf + sizeof buf - 1 - 10 - 1 - - strlen(__progname) - 1; - t = buf; - s = profdir; - while((*t = *s) != '\0' && t < limit) { - t++; - s++; - } - *t++ = '/'; - - /* - * Copy and convert pid from a pid_t to a string. For - * best performance, divisor should be initialized to - * the largest power of 10 less than PID_MAX. - */ - pid = getpid(); - divisor=10000; - while (divisor > pid) divisor /= 10; /* skip leading zeros */ - do { - *t++ = (pid/divisor) + '0'; - pid %= divisor; - } while (divisor /= 10); - *t++ = '.'; - - s = __progname; - while ((*t++ = *s++) != '\0') - ; - - proffile = buf; - } else { - proffile = "gmon.out"; - } - - fd = open(proffile , O_CREAT|O_TRUNC|O_WRONLY, 0664); - if (fd == -1) { - perror( proffile ); - return; - } -#ifdef DEBUG - log = open("gmon.log", O_CREAT|O_TRUNC|O_WRONLY, 0664); - if (log == -1) { - perror("mcount: gmon.log"); - close(fd); - return; - } - snprintf(dbuf, sizeof dbuf, "[mcleanup1] kcount 0x%x ssiz %d\n", - p->kcount, p->kcountsize); - write(log, dbuf, strlen(dbuf)); -#endif + /* First, serialize the gmon header. */ hdr = (struct gmonhdr *)&gmonhdr; bzero(hdr, sizeof(*hdr)); hdr->lpc = p->lowpc; @@ -238,8 +179,48 @@ _mcleanup(void) hdr->ncnt = p->kcountsize + sizeof(gmonhdr); hdr->version = GMONVERSION; hdr->profrate = clockinfo.profhz; - write(fd, (char *)hdr, sizeof *hdr); - write(fd, p->kcount, p->kcountsize); + len = snprintf(ubuf, sizeof ubuf, "gmonhdr %lx %lx %x %x %x", + hdr->lpc, hdr->hpc, hdr->ncnt, hdr->version, hdr->profrate); + if (len == -1 || len >= sizeof ubuf) + goto out; + if (utrace(GMON_LABEL, ubuf, len) == -1) + goto out; + + /* + * Next, serialize the kcount sample array. Each trace is prefixed + * with the string "kcount" (6). Each sample is prefixed with a + * delimiting space (1) and serialized as a 4-digit hexadecimal + * value (4). The buffer, ubuf, is KTR_USER_MAXLEN + 1 bytes, but + * each trace is limited to KTR_USER_MAXLEN bytes. Given these + * constraints, we can fit at most: + * + * floor((KTR_USER_MAXLEN - 6) / (4 + 1) + * = floor((KTR_USER_MAXLEN - 6) / 5) + * + * samples per trace. + */ + assert(sizeof(*p->kcount) == 2); + sample_total = p->kcountsize / sizeof(*p->kcount); + sample_limit = (sizeof(ubuf) - 6) / 5; + for (i = 0; i < sample_total; i = j) { + off = strlcpy(ubuf, "kcount", sizeof ubuf); + assert(off == 6); + if (sample_total - i < sample_limit) + limit = sample_total; + else + limit = i + sample_limit; + for (j = i; j < limit; j++) { + len = snprintf(ubuf + off, sizeof(ubuf) - off, + " %04hx", p->kcount[j]); + assert(len == 5); + off += len; + assert(off < sizeof ubuf); + } + if (utrace(GMON_LABEL, ubuf, off) == -1) + goto out; + } + + /* Last, serialize the arcs. One per trace. */ endfrom = p->fromssize / sizeof(*p->froms); for (fromindex = 0; fromindex < endfrom; fromindex++) { if (p->froms[fromindex] == 0) @@ -249,20 +230,29 @@ _mcleanup(void) frompc += fromindex * p->hashfraction * sizeof(*p->froms); for (toindex = p->froms[fromindex]; toindex != 0; toindex = p->tos[toindex].link) { -#ifdef DEBUG - (void) snprintf(dbuf, sizeof dbuf, - "[mcleanup2] frompc 0x%x selfpc 0x%x count %d\n" , - frompc, p->tos[toindex].selfpc, - p->tos[toindex].count); - write(log, dbuf, strlen(dbuf)); -#endif rawarc.raw_frompc = frompc; rawarc.raw_selfpc = p->tos[toindex].selfpc; rawarc.raw_count = p->tos[toindex].count; - write(fd, &rawarc, sizeof rawarc); + len = snprintf(ubuf, sizeof ubuf, "rawarc %lx %lx %lx", + rawarc.raw_frompc, rawarc.raw_selfpc, + rawarc.raw_count); + if (len == -1 || len >= sizeof ubuf) + goto out; + if (utrace(GMON_LABEL, ubuf, len) == -1) + goto out; } } - close(fd); + + /* + * Leave a footer so the reader knows they have the full dump. + * This is a convenience for the reader: it is not a part of + * the gmon binary. + */ + off = strlcpy(ubuf, "footer", sizeof ubuf); + assert(off == 6); + utrace(GMON_LABEL, ubuf, off); +out: + /* nothing */; #ifdef notyet if (p->kcount != NULL) { munmap(p->kcount, p->kcountsize); Index: usr.bin/gprof/gprof.c =================================================================== RCS file: /cvs/src/usr.bin/gprof/gprof.c,v retrieving revision 1.27 diff -u -p -r1.27 gprof.c --- usr.bin/gprof/gprof.c 27 Jan 2021 07:18:41 -0000 1.27 +++ usr.bin/gprof/gprof.c 19 Jun 2023 03:15:15 -0000 @@ -70,6 +70,7 @@ bool eflag; bool Eflag; /* functions excluded with time */ bool fflag; /* specific functions requested */ bool Fflag; /* functions requested with time */ +bool gflag; /* profile inputs are raw gmon files */ bool kflag; /* arcs to be deleted */ bool sflag; /* sum multiple gmon.out files */ bool zflag; /* zero time/called functions, too */ @@ -142,6 +143,9 @@ main(int argc, char *argv[]) addlist( flist , *++argv ); fflag = TRUE; break; + case 'g': + gflag = TRUE; + break; case 'k': addlist( kfromlist , *++argv ); addlist( ktolist , *++argv ); @@ -168,7 +172,7 @@ main(int argc, char *argv[]) } else { gmonname = GMONNAME; } - if ( sflag == FALSE ) { + if ( gflag == TRUE && sflag == FALSE ) { if (pledge("stdio rpath", NULL) == -1) err(1, "pledge"); } @@ -273,12 +277,23 @@ FILE * openpfile(const char *filename) { struct gmonhdr tmp; - FILE *pfile; + FILE *ktrace, *pfile; int size; int rate; - if((pfile = fopen(filename, "r")) == NULL) - err(1, "fopen: %s", filename); + if (gflag) { + if ((pfile = fopen(filename, "r")) == NULL) + err(1, "fopen: %s", filename); + } else { + ktrace = fopen(filename, "r"); + if (ktrace == NULL) + err(1, "fopen: %s", filename); + pfile = ktrace_extract(ktrace, filename); + if (pfile == NULL) + errx(1, "%s: ktrace extraction failed", filename); + if (fclose(ktrace) == EOF) + err(1, "fclose: %s", filename); + } if (fread(&tmp, sizeof(struct gmonhdr), 1, pfile) != 1) errx(1, "%s: bad gmon header", filename); if ( s_highpc != 0 && ( tmp.lpc != gmonhdr.lpc || Index: usr.bin/gprof/extract.c =================================================================== RCS file: usr.bin/gprof/extract.c diff -N usr.bin/gprof/extract.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ usr.bin/gprof/extract.c 19 Jun 2023 03:15:15 -0000 @@ -0,0 +1,317 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2023 Sebastien Marie <sema...@openbsd.org> + * Copyright (c) 2023 Scott Cheloha <chel...@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/gmon.h> +#include <sys/ktrace.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define GMON_LABEL "_openbsd_libc_gmon" +#define GMON_LABEL_LEN (sizeof(GMON_LABEL) - 1) + +/* + * A rudimentary gmon.out deserialization state machine. + * Allows for basic error-checking and the detection of an + * incomplete record set. + */ +enum gmon_state { + HEADER, + KCOUNT, + RAWARC, + FOOTER, + ERROR +}; + +struct gmon_de { + size_t sample_count; /* kcount array: current sample count */ + size_t sample_total; /* kcount array: total samples in array */ + enum gmon_state state; /* gmon.out deserialization step */ +}; + +void de_warnx(const char *, const char *, ...); +void gmon_append(FILE *, const char *, struct gmon_de *, const char *, char *); +int ktrace_header(FILE *, struct ktr_header *); +int ktrace_next(FILE *, const char *, struct ktr_header *, void **, size_t *); + +FILE * +ktrace_extract(FILE *kfp, const char *ktrace_path) +{ + struct _user_trace { + struct ktr_user hdr; + char buf[KTR_USER_MAXLEN + 1]; /* +1 for NUL */ + } *user_trace; + char temp_path[32]; + struct gmon_de de = { .state = HEADER }; + struct ktr_header header = { 0 }; + FILE *tfp; + void *buf = NULL, *label; + size_t buf_size = 0, len; + int fd, have_pid = 0, saved_errno; + pid_t pid; + + /* Deserialize moncontrol(3) records into a temporary file. */ + len = strlcpy(temp_path, "/tmp/gmon.out.XXXXXXXXXX", sizeof temp_path); + assert(len < sizeof temp_path); + fd = mkstemp(temp_path); + if (fd == -1) { + warn("mkstemp"); + return NULL; + } + + /* + * We have opened a file descriptor. From this point on, + * we need to to jump to "error" and clean up before returning. + */ + if (unlink(temp_path) == -1) { + warn("unlink: %s", temp_path); + goto error; + } + tfp = fdopen(fd, "r+"); + if (tfp == NULL) { + warn("%s", temp_path); + goto error; + } + + if (ktrace_header(kfp, &header) == -1) { + warn("%s", ktrace_path); + goto error; + } + if (header.ktr_type != htobe32(KTR_START)) { + warn("%s: not a valid ktrace file", ktrace_path); + goto error; + } + + while (ktrace_next(kfp, ktrace_path, &header, &buf, &buf_size) != -1) { + /* Filter for utrace(2) headers with the gmon label. */ + if (header.ktr_type != KTR_USER) + continue; + user_trace = buf; + label = &user_trace->hdr.ktr_id; + if (memcmp(label, GMON_LABEL, GMON_LABEL_LEN) != 0) + continue; + + /* Only consider the first gmon.out record set. */ + if (!have_pid) { + pid = header.ktr_pid; + have_pid = 1; + } + if (have_pid && pid != header.ktr_pid) + continue; + + /* Append the next piece. */ + gmon_append(tfp, temp_path, &de, ktrace_path, user_trace->buf); + if (de.state == FOOTER || de.state == ERROR) + break; + } + if (ferror(kfp)) { + warn("%s", ktrace_path); + goto error; + } + + if (de.state == ERROR) + goto error; + if (de.state == HEADER) { + warnx("%s: no moncontrol record set found", ktrace_path); + goto error; + } + if (de.state != FOOTER) { + warnx("%s: found incomplete moncontrol record set", + ktrace_path); + goto error; + } + + /* + * We have a complete gmon.out file. Flush and rewind the + * handle so the caller can reread it. + */ + if (fflush(tfp) == EOF) { + warn("%s", temp_path); + goto error; + } + if (fseek(tfp, 0, SEEK_SET) == -1) { + warn("%s", temp_path); + goto error; + } + + return tfp; +error: + free(buf); + saved_errno = errno; + if (close(fd) == -1) + warn("close: %s", temp_path); + errno = saved_errno; + return NULL; +} + +void +de_warnx(const char *ktrace_path, const char *fmt, ...) +{ + int saved_errno = errno; + va_list ap; + + fprintf(stderr, "%s: %s: deserialization failed: ", + getprogname(), ktrace_path); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + errno = saved_errno; +} + +void +gmon_append(FILE *fp, const char *path, struct gmon_de *de, + const char *ktrace_path, char *trace) +{ + struct gmonhdr header; + struct rawarc arc; + char *p; + int count; + uint16_t sample; + + switch (de->state) { + case HEADER: + memset(&header, 0, sizeof header); + count = sscanf(trace, "gmonhdr %lx %lx %x %x %x", + &header.lpc, &header.hpc, &header.ncnt, &header.version, + &header.profrate); + if (count != 5) { + de_warnx(ktrace_path, "gmonhdr: %s", trace); + goto error; + } + if (header.ncnt < sizeof header) { + de_warnx(ktrace_path, "gmonhdr: ncnt is invalid: %d", + header.ncnt); + goto error; + } + if (fwrite(&header, sizeof header, 1, fp) != 1) { + warn("%s", path); + goto error; + } + de->sample_count = 0; + de->sample_total = (header.ncnt - sizeof(header)) / 2; + de->state = KCOUNT; + return; + case KCOUNT: + p = strsep(&trace, " "); + if (p == NULL || strcmp(p, "kcount") != 0) { + de_warnx(ktrace_path, "kcount: %s", + p == NULL ? trace : p); + goto error; + } + while ((p = strsep(&trace, " ")) != NULL) { + if (strlen(p) != 4) { + de_warnx(ktrace_path, + "kcount: sample %zu/%zu is invalid: %s", + de->sample_count, de->sample_total, p); + goto error; + } + if (de->sample_count == de->sample_total) { + de_warnx(ktrace_path, + "kcount: found more than %zu samples", + de->sample_total); + goto error; + } + sample = 0; + for (; *p != '\0'; p++) { + if (*p < '0' || 'f' < *p) { + de_warnx(ktrace_path, "kcount: " + "sample %zu/%zu is invalid: %s", + de->sample_count, + de->sample_total, p); + goto error; + } + sample = sample * 16 + (*p - '0'); + } + if (fwrite(&sample, sizeof sample, 1, fp) != 1) { + warn("%s", path); + goto error; + } + de->sample_count++; + } + if (de->sample_count == de->sample_total) + de->state = RAWARC; + return; + case RAWARC: + if (strcmp(trace, "footer") == 0) { + de->state = FOOTER; + return; + } + memset(&arc, 0, sizeof arc); + count = sscanf(trace, "rawarc %lx %lx %lx", + &arc.raw_frompc, &arc.raw_selfpc, &arc.raw_count); + if (count != 3) { + de_warnx(ktrace_path, "rawarc: %s", trace); + goto error; + } + if (fwrite(&arc, sizeof arc, 1, fp) != 1) { + warn("%s", path); + goto error; + } + return; + case FOOTER: + case ERROR: + default: + abort(); + } + +error: + de->state = ERROR; +} + +int +ktrace_header(FILE *fp, struct ktr_header *header) +{ + if (fread(header, sizeof(*header), 1, fp) == 1) + return 0; + return -1; +} + +int +ktrace_next(FILE *fp, const char *ktrace_path, struct ktr_header *header, + void **bufp, size_t *sizep) +{ + void *new_buf; + size_t new_size; + + if (ktrace_header(fp, header) == -1) + return -1; + if (header->ktr_len == 0) + errx(1, "%s: invalid trace: ktr_len is zero", ktrace_path); + if (header->ktr_len > *sizep) { + new_size = header->ktr_len + 1; /* +1 for NUL */ + new_buf = realloc(*bufp, new_size); + if (new_buf == NULL) + err(1, NULL); + *bufp = new_buf; + *sizep = new_size; + } + memset(*bufp, 0, *sizep); + if (fread(*bufp, header->ktr_len, 1, fp) != 1) + return -1; + return 0; +} Index: usr.bin/gprof/gprof.h =================================================================== RCS file: /cvs/src/usr.bin/gprof/gprof.h,v retrieving revision 1.17 diff -u -p -r1.17 gprof.h --- usr.bin/gprof/gprof.h 27 Jan 2021 07:18:41 -0000 1.17 +++ usr.bin/gprof/gprof.h 19 Jun 2023 03:15:15 -0000 @@ -263,6 +263,7 @@ void gprofheader(void); void gprofline(nltype *); int hertz(void); void inheritflags(nltype *); +FILE * ktrace_extract(FILE *, const char *); unsigned long max(unsigned long, unsigned long); int membercmp(nltype *, nltype *); unsigned long min(unsigned long, unsigned long); Index: usr.bin/gprof/Makefile =================================================================== RCS file: /cvs/src/usr.bin/gprof/Makefile,v retrieving revision 1.21 diff -u -p -r1.21 Makefile --- usr.bin/gprof/Makefile 17 Oct 2013 10:51:57 -0000 1.21 +++ usr.bin/gprof/Makefile 19 Jun 2023 03:15:15 -0000 @@ -6,7 +6,7 @@ TARGET_MACHINE_ARCH?= ${MACHINE_ARCH} TARGET_MACHINE_CPU?= ${MACHINE_CPU} PROG= gprof -SRCS= gprof.c arcs.c dfn.c elf.c lookup.c ${TARGET_MACHINE_CPU}.c \ +SRCS= gprof.c arcs.c dfn.c elf.c extract.c lookup.c ${TARGET_MACHINE_CPU}.c \ hertz.c printgprof.c printlist.c CFLAGS+= -I. -DMD_INCLUDE=\"${TARGET_MACHINE_CPU}.h\"