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\"
 

Reply via email to