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);