Search code examples
dtrace

Dtrace from C does not yield same profiling result compared to command line


I want to programmatically trace stacks from C for Node.js (JS addresses aside).

The following command gives me stacks with resolved c++ symbols.

sudo dtrace -C -n 'profile-101 /pid == 13221/ { ustack() }'

The following C code only returns addresses for Node's C/C++ code. What would be the equivalent?

#include <dtrace.h>
#include <signal.h>
#include <stdio.h>

struct ps_prochandle *g_pr;
static dtrace_hdl_t* g_dtp;

static int chewrec (const dtrace_probedata_t *data, const dtrace_recdesc_t *rec, void *arg) {
   // A NULL rec indicates that we've processed the last record.
   if (rec == NULL) {
      return (DTRACE_CONSUME_NEXT);
   }
   return (DTRACE_CONSUME_THIS);
}

static const char* g_prog =
  "#pragma D option switchrate=1000hz\n"
  "profile-1ms /pid == 13221/ {\n"
    "ustack();\n"
  "}";

static int g_intr;
static int g_exited;

static void intr (int signo) {
   g_intr = 1;
}


int main (int argc, char** argv) {
   int err;

   if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) {
      fprintf(stderr, "failed to initialize dtrace: %s\n", dtrace_errmsg(NULL, err));
      return -1;
   }
   printf("Dtrace initialized\n");

   (void) dtrace_setopt(g_dtp, "bufsize", "4m");
   (void) dtrace_setopt(g_dtp, "aggsize", "4m");

   printf("dtrace options set\n");

   dtrace_prog_t* prog;
   if ((prog = dtrace_program_strcompile(g_dtp, g_prog, DTRACE_PROBESPEC_NAME, DTRACE_C_CPP, 0, NULL)) == NULL) {
      fprintf(stderr, "failed to compile dtrace program\n");
      return -1;
   } else {
      printf("dtrace program compiled\n");
   }

   dtrace_proginfo_t info;
   if (dtrace_program_exec(g_dtp, prog, &info) == -1) {
      fprintf(stderr, "failed to enable dtrace probes\n");
      return -1;
   } else {
      printf("dtrace probes enabled\n");
   }

   struct sigaction act;
   (void) sigemptyset(&act.sa_mask);
   act.sa_flags = 0;
   act.sa_handler = intr;
   (void) sigaction(SIGINT, &act, NULL);
   (void) sigaction(SIGTERM, &act, NULL);

   if (dtrace_go(g_dtp) != 0) {
      fprintf(stderr, "could not start instrumentation\n");
      return -1;
   } else {
      printf("instrumentation started ..\n");
   }

   int done = 0;
   do {
      if (!g_intr && !done) {
         dtrace_sleep(g_dtp);
      }

      if (done || g_intr || g_exited) {
         done = 1;
         if (dtrace_stop(g_dtp) == -1) {
            fprintf(stderr, "could not stop tracing\n");
            return -1;
         }
      }

      switch (dtrace_work(g_dtp, stdout, NULL, chewrec, NULL)) {
         case DTRACE_WORKSTATUS_DONE:
            done = 1;
            break;
         case DTRACE_WORKSTATUS_OKAY:
            break;
         default:
            fprintf(stderr, "processing aborted");
            return -1;
      }
   } while (!done);

   printf("closing dtrace\n");
   dtrace_close(g_dtp);

   return 0;
}

Solution

  • From the dtrace-Mailing list, Robert Mustacchi:

    TL;DR resolve symbols yourself in userland.

    "So, all the symbol resolution processing is always done in user land. In other words, DTrace in the kernel only ever gathers addresses like you're seeing for the cases where you're using ustack and not a ustack handler via the jstack() action (jstack() also only returns symbols for non-native frames). Note that if you want to see the JavaScript specific symbols in addition to the native ones, you'll want to be using jstack() in your examples and not ustack().

    The way that these you can perform these mappings will change depending on what system you're on. If you look at what DTrace does on illumos for printing the results of ustack() (http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libdtrace/common/dt_consume.c#1320), then you'll see that it uses libproc and the Plookup_by_addr() function to perform the translation. Though it's worth pointing out that neither are stable interfaces, though they seldom change."