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 <[email protected]>
+ * Copyright (c) 2023 Scott Cheloha <[email protected]>
+ *
+ * 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\"