Hi, After otto@ work on mallocdump using utrace(2), I started to look again at profiling (see moncontrol(2)).
The current implementation tries to write a gmon.out file at program exit(3) time, which isn't compatible with pledge(2) or unveil(2). This diff changes the way the runtime profiling information is extracted by using utrace(2) (which is permitted while pledged). The information is collected using ktrace(2), and the gmon.out file could be recreated from ktrace.out file post-execution (so without unveil(2) restriction). One place where I needed to cheat a bit is at moncontrol(0) call in _mcleanup() function. moncontrol(0) will stop profiling (change the state to GMON_PROF_OFF), and it is calling profil(2) to disable program counter profiling. _mcleanup() is the atexit(3) handler. Instead of changing pledge(2) permission for profil(2) (which could go deep in the kernel to change the clockrate with setstatclockrate()), I just assumed that it isn't necessary to disable it here: we are in atexit(3), so the process is about to call _exit(2), and the kernel will stop the profiling itself if still running. For post-execution gmon.out extraction, an helper might be needed: ktrace.out could contain multiple gmon.out files. ## compile and collect profil information (-tu option on ktrace is optional) $ cc -static -pg test.c $ ktrace -di -tu ./a.out ## get gmon.out file $ kdump -u gmon.out | unvis > gmon.out ## get gmon.out.$name.$pid for multiple processes ## - first get pid process-name ## - extract each gmon.out for each pid and store in "gmon.out.$name.$pid" file $ kdump -tu | sed -ne 's/^ \([0-9][0-9]*\) \([^ ]*\) .*/\1 \2/p' | sort -u \ | while read pid name; do kdump -u gmon.out -p $pid | unvis > gmon.out.$name.$pid ; done kdump diff from otto@ mallocdump is need for 'kdump -u label'. Feedback would be appreciated. -- Sebastien Marie diff /home/semarie/repos/openbsd/src commit - 7639070d00278d5f3d76cbc265d756c39619d7a8 path + /home/semarie/repos/openbsd/src blob - f09ffd91837040d44fb17a9e8c0197c72ba9459a file + lib/libc/gmon/gmon.c --- lib/libc/gmon/gmon.c +++ lib/libc/gmon/gmon.c @@ -29,6 +29,7 @@ */ #include <sys/time.h> +#include <sys/ktrace.h> #include <sys/gmon.h> #include <sys/mman.h> #include <sys/sysctl.h> @@ -51,6 +52,32 @@ void PROTO_NORMAL(moncontrol); PROTO_DEPRECATED(monstartup); +static void +montrace(void *addr, size_t len) +{ + for (;;) { + if (len < KTR_USER_MAXLEN) { + if (utrace("gmon.out", addr, len) == -1) + ERR("error on utrace(), truncated gmon.out"); + return; + } + if (utrace("gmon.out", addr, KTR_USER_MAXLEN) == -1) + ERR("error on utrace(), truncated gmon.out"); + + len -= KTR_USER_MAXLEN; + addr += KTR_USER_MAXLEN; + } +} + +#ifdef DEBUG +static int +monlog(char *msg) +{ + size_t len = strlen(msg); + return utrace("gmon.log", msg, len); +} +#endif + void monstartup(u_long lowpc, u_long highpc) { @@ -136,7 +163,6 @@ _mcleanup(void) void _mcleanup(void) { - int fd; int fromindex; int endfrom; u_long frompc; @@ -147,11 +173,8 @@ _mcleanup(void) 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; + int len; char dbuf[200]; #endif @@ -169,67 +192,16 @@ _mcleanup(void) clockinfo.profhz = clockinfo.hz; /* best guess */ } - moncontrol(0); + /* + * Do not call moncontrol(0) (neither profil(2)) as we might be pledged. + * We are in _mcleanup(), so the process is inside exit(3). + */ + p->state = GMON_PROF_OFF; - 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)); + monlog(dbuf); #endif hdr = (struct gmonhdr *)&gmonhdr; bzero(hdr, sizeof(*hdr)); @@ -238,8 +210,8 @@ _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); + montrace(hdr, sizeof *hdr); + montrace(p->kcount, p->kcountsize); endfrom = p->fromssize / sizeof(*p->froms); for (fromindex = 0; fromindex < endfrom; fromindex++) { if (p->froms[fromindex] == 0) @@ -254,15 +226,14 @@ _mcleanup(void) "[mcleanup2] frompc 0x%x selfpc 0x%x count %d\n" , frompc, p->tos[toindex].selfpc, p->tos[toindex].count); - write(log, dbuf, strlen(dbuf)); + monlog(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); + montrace(&rawarc, sizeof rawarc); } } - close(fd); #ifdef notyet if (p->kcount != NULL) { munmap(p->kcount, p->kcountsize);