perf: Use PERF_SAMPLE_IDENTIFIER
[akaros.git] / tools / profile / perf / perfconv.c
index 3b8aa92..fdcd2f5 100644 (file)
@@ -1,5 +1,7 @@
-/* Copyright (c) 2015 Google Inc
+/* Copyright (c) 2015-2016 Google Inc
  * Davide Libenzi <dlibenzi@google.com>
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ *
  * See LICENSE for details.
  *
  * Converts kprof profiler files into Linux perf ones. The Linux Perf file
@@ -27,6 +29,7 @@
 #include "xlib.h"
 #include "perf_core.h"
 #include "perfconv.h"
+#include "elf.h"
 
 #define MAX_PERF_RECORD_SIZE (32 * 1024 * 1024)
 #define PERF_RECORD_BUFFER_SIZE 1024
@@ -35,6 +38,8 @@
 
 #define MBWR_SOLID (1 << 0)
 
+char *cmd_line_save;
+
 struct perf_record {
        uint64_t type;
        uint64_t size;
@@ -89,10 +94,9 @@ static int read_record(FILE *file, struct perf_record *pr)
        return 0;
 }
 
-static struct mem_block *mem_block_alloc(struct mem_file *mf, size_t size)
+static struct mem_block *mem_block_alloc(size_t size)
 {
-       struct mem_block *mb = xmem_arena_alloc(mf->ma,
-                                                                                       sizeof(struct mem_block) + size);
+       struct mem_block *mb = xmalloc(sizeof(struct mem_block) + size);
 
        mb->next = NULL;
        mb->base = mb->wptr = (char *) mb + sizeof(struct mem_block);
@@ -114,10 +118,9 @@ static char *mem_block_write(struct mem_block *mb, const void *data,
        return wrbase;
 }
 
-static void mem_file_init(struct mem_file *mf, struct mem_arena *ma)
+static void mem_file_init(struct mem_file *mf)
 {
        ZERO_DATA(*mf);
-       mf->ma = ma;
 }
 
 static int mem_block_can_write(struct mem_block *mb, size_t size, int flags)
@@ -137,7 +140,7 @@ static void *mem_file_write(struct mem_file *mf, const void *data, size_t size,
                struct mem_block *mb = mf->tail;
 
                if (!mb || !mem_block_can_write(mb, size, flags)) {
-                       mb = mem_block_alloc(mf, max(MEMFILE_BLOCK_SIZE, size));
+                       mb = mem_block_alloc(max(MEMFILE_BLOCK_SIZE, size));
                        if (!mf->tail)
                                mf->head = mb;
                        else
@@ -177,8 +180,7 @@ static void mem_file_sync(struct mem_file *mf, FILE *file, uint64_t rel_offset)
 static struct mem_file_reloc *mem_file_add_reloc(struct mem_file *mf,
                                                                                                 uint64_t *ptr)
 {
-       struct mem_file_reloc *rel =
-               xmem_arena_zalloc(mf->ma, sizeof(struct mem_file_reloc));
+       struct mem_file_reloc *rel = xzmalloc(sizeof(struct mem_file_reloc));
 
        rel->ptr = ptr;
        rel->next = mf->relocs;
@@ -192,7 +194,7 @@ void perfconv_add_kernel_mmap(struct perfconv_context *cctx)
        char path[] = "[kernel.kallsyms]";
        struct static_mmap64 *mm;
 
-       mm = xmem_arena_zalloc(&cctx->ma, sizeof(struct static_mmap64));
+       mm = xzmalloc(sizeof(struct static_mmap64));
        mm->pid = -1;                           /* Linux HOST_KERNEL_ID == -1 */
        mm->tid = 0;                            /* Default thread: swapper */
        mm->header_misc = PERF_RECORD_MISC_KERNEL;
@@ -204,7 +206,7 @@ void perfconv_add_kernel_mmap(struct perfconv_context *cctx)
        mm->addr = 0;
        mm->size = 0xffffffffffffffff;
        mm->offset = 0x0;
-       mm->path = xmem_arena_strdup(&cctx->ma, path);
+       mm->path = xstrdup(path);
 
        mm->next = cctx->static_mmaps;
        cctx->static_mmaps = mm;
@@ -215,29 +217,301 @@ static void headers_init(struct perf_headers *hdrs)
        ZERO_DATA(*hdrs);
 }
 
-static void headers_add_header(struct perf_headers *hdrs, size_t nhdr,
+/* Prepends a header (the mem_block) to the list of memblocks for the given
+ * HEADER type.  They will all be concatted together during finalization. */
+static void headers_add_header(struct perf_header *ph,
+                               struct perf_headers *hdrs, size_t nhdr,
                                                           struct mem_block *mb)
 {
        always_assert(nhdr < HEADER_FEAT_BITS);
 
+       mb->next = hdrs->headers[nhdr];
        hdrs->headers[nhdr] = mb;
+       set_bitno(ph->adds_features, nhdr);
 }
 
-static void headers_write(struct perf_headers *hdrs, struct perf_header *ph,
-                                                 struct mem_file *mf)
+/* Emits the headers contents to the mem_file */
+static void headers_finalize(struct perf_headers *hdrs, struct mem_file *mf)
 {
-       size_t i;
-
-       for (i = 0; i < HEADER_FEAT_BITS; i++) {
-               struct mem_block *mb = hdrs->headers[i];
+       struct perf_file_section *header_file_secs, *file_sec;
+       struct perf_file_section *file_sec_reloc;
+       size_t nr_hdrs = 0;
+       size_t hdr_off;
+       struct mem_block *mb;
+       size_t mb_sz;
+
+       /* For each header, we need a perf_file_section.  These header file sections
+        * are right after the main perf header, and they point to actual header. */
+       for (int i = 0; i < HEADER_FEAT_BITS; i++)
+               if (hdrs->headers[i])
+                       nr_hdrs++;
+       if (!nr_hdrs)
+               return;
+       header_file_secs = xmalloc(sizeof(struct perf_file_section) * nr_hdrs);
+
+       hdr_off = sizeof(struct perf_file_section) * nr_hdrs;
+       file_sec = header_file_secs;
+
+       /* Spit out the perf_file_sections first and track relocations for all of
+        * the offsets. */
+       for (int i = 0; i < HEADER_FEAT_BITS; i++) {
+               mb = hdrs->headers[i];
+               if (!mb)
+                       continue;
+               mb_sz = mb->wptr - mb->base;
+               /* headers[i] is a chain of memblocks */
+               while (mb->next) {
+                       mb = mb->next;
+                       mb_sz += mb->wptr - mb->base;
+               }
+               file_sec->size = mb_sz;
+               file_sec->offset = hdr_off;             /* offset rel to this memfile */
+               /* When we sync the memfile, we'll need to relocate each of the offsets
+                * so that they are relative to the final file.   mem_file_write()
+                * should be returning the location of where it wrote our file section
+                * within the memfile.  that's the offset that we'll reloc later. */
+               file_sec_reloc = mem_file_write(mf, file_sec,
+                                               sizeof(struct perf_file_section), 0);
+               assert(file_sec->size == file_sec_reloc->size);
+               assert(file_sec->offset == file_sec_reloc->offset);
+               mem_file_add_reloc(mf, &file_sec_reloc->offset);
+
+               hdr_off += mb_sz;
+               file_sec++;
+       }
+       free(header_file_secs);
 
-               if (mb) {
+       /* Spit out the actual headers */
+       for (int i = 0; i < HEADER_FEAT_BITS; i++) {
+               mb = hdrs->headers[i];
+               while (mb) {
                        mem_file_write(mf, mb->base, mb->wptr - mb->base, 0);
-                       set_bitno(ph->adds_features, i);
+                       mb = mb->next;
                }
        }
 }
 
+/* Builds a struct perf_header_string from str and returns it in a mem_block */
+static struct mem_block *header_make_string(const char *str)
+{
+       struct perf_header_string *hdr;
+       struct mem_block *mb;
+       size_t str_sz = strlen(str) + 1;
+       size_t hdr_sz = ROUNDUP(str_sz + sizeof(struct perf_header_string),
+                               PERF_STRING_ALIGN);
+
+       mb = mem_block_alloc(hdr_sz);
+       /* Manually writing to the block to avoid another alloc.  I guess I could do
+        * two writes (len and string) and try to not screw it up, but that'd be a
+        * mess. */
+       hdr = (struct perf_header_string*)mb->wptr;
+       mb->wptr += hdr_sz;
+       hdr->len = str_sz;
+       memcpy(hdr->string, str, str_sz);
+       return mb;
+}
+
+/* Opens and reads filename, returning the contents.  Free the ret. */
+static char *get_str_from_os(const char *filename)
+{
+       int fd, ret;
+       struct stat fd_stat;
+       char *buf;
+       size_t buf_sz;
+
+       fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return 0;
+       ret = fstat(fd, &fd_stat);
+       if (ret) {
+               close(fd);
+               return 0;
+       }
+       buf_sz = fd_stat.st_size + 1;
+       buf = xmalloc(buf_sz);
+       ret = read(fd, buf, buf_sz - 1);
+       if (ret <= 0) {
+               free(buf);
+               close(fd);
+               return 0;
+       }
+       close(fd);
+       /* OS strings should be null terminated, but let's be defensive */
+       buf[ret] = 0;
+       return buf;
+}
+
+static void hdr_do_osrelease(struct perf_header *ph, struct perf_headers *hdrs)
+{
+       char *str;
+
+       str = get_str_from_os("#version/version_name");
+       if (!str)
+               return;
+       headers_add_header(ph, hdrs, HEADER_OSRELEASE, header_make_string(str));
+       free(str);
+}
+
+static void hdr_do_nrcpus(struct perf_header *ph, struct perf_headers *hdrs)
+{
+       char *str;
+       uint32_t nr_cores;
+       struct mem_block *mb;
+       struct nr_cpus *hdr;
+
+       str = get_str_from_os("#vars/num_cores!dw");
+       if (!str)
+               return;
+       nr_cores = atoi(str);
+       free(str);
+
+       mb = mem_block_alloc(sizeof(struct nr_cpus));
+       hdr = (struct nr_cpus*)mb->wptr;
+       mb->wptr += sizeof(struct nr_cpus);
+
+       hdr->nr_cpus_online = nr_cores;
+       hdr->nr_cpus_available = nr_cores;
+
+       headers_add_header(ph, hdrs, HEADER_NRCPUS, mb);
+}
+
+/* Unfortunately, HEADER_CMDLINE doesn't just take a string.  It takes a list of
+ * null-terminated perf_header_strings (i.e. argv), with the a u32 for the
+ * number of strings.  We can just send one string for the entire cmd line. */
+static void hdr_do_cmdline(struct perf_header *ph, struct perf_headers *hdrs)
+{
+       struct perf_header_string *hdr;
+       struct mem_block *mb;
+       size_t str_sz = strlen(cmd_line_save) + 1;
+       size_t hdr_sz = sizeof(uint32_t) +
+                       ROUNDUP(str_sz + sizeof(struct perf_header_string),
+                               PERF_STRING_ALIGN);
+
+       mb = mem_block_alloc(hdr_sz);
+       /* emit the nr strings (1) */
+       *(uint32_t*)mb->wptr = 1;
+       mb->wptr += sizeof(uint32_t);
+       /* emit the perf_header_string, as usual */
+       hdr = (struct perf_header_string*)mb->wptr;
+       mb->wptr += hdr_sz - sizeof(uint32_t);
+       hdr->len = str_sz;
+       memcpy(hdr->string, cmd_line_save, str_sz);
+
+       headers_add_header(ph, hdrs, HEADER_CMDLINE, mb);
+}
+
+/* Returns TRUE if we already emitted a build-id for path.  If this gets too
+ * slow (which we'll know because of perf!) we can use a hash table (don't hash
+ * on the first few bytes btw - they are usually either /bin or /lib). */
+static bool lookup_buildid(struct perfconv_context *cctx, const char *path)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       size_t b_evt_path_sz;
+
+       mb = cctx->hdrs.headers[HEADER_BUILD_ID];
+       while (mb) {
+               b_evt = (struct build_id_event*)mb->base;
+               b_evt_path_sz = b_evt->header.size -
+                               offsetof(struct build_id_event, filename);
+               /* ignoring the last byte since we forced it to be \0 earlier. */
+               if (!strncmp(b_evt->filename, path, b_evt_path_sz - 1))
+                       return TRUE;
+               mb = mb->next;
+       }
+       return FALSE;
+}
+
+/* Helper: given a path, allocs and inits a build_id_event within a mem_block,
+ * returning both via parameters.  Caller needs to set header.misc and fill in
+ * the actual build_id. */
+static void build_id_alloc(const char *path, struct build_id_event **b_evt_p,
+                           struct mem_block **mb_p)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       size_t path_sz, b_evt_sz;
+
+       path_sz = strlen(path) + 1;
+       b_evt_sz = path_sz + sizeof(struct build_id_event);
+
+       mb = mem_block_alloc(b_evt_sz);
+       b_evt = (struct build_id_event*)mb->wptr;
+       mb->wptr += b_evt_sz;
+
+       b_evt->header.type = 0; /* if this fails, try 67 (synthetic build id) */
+       /* header.size filled in by the caller, depending on the type */
+       b_evt->header.size = b_evt_sz;
+       strlcpy(b_evt->filename, path, path_sz);
+
+       *b_evt_p = b_evt;
+       *mb_p = mb;
+}
+
+/* Add a build-id header.  Unlike many of the other headers, this one is built
+ * on the fly as we emit other records. */
+static void hdr_add_buildid(struct perfconv_context *cctx, const char *path,
+                            int pid)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       int ret;
+
+       if (lookup_buildid(cctx, path))
+               return;
+
+       build_id_alloc(path, &b_evt, &mb);
+       b_evt->header.misc = PERF_RECORD_MISC_USER;
+       b_evt->pid = pid;
+       ret = filename__read_build_id(path, b_evt->build_id, BUILD_ID_SIZE);
+       if (ret <= 0)
+               free(mb);
+       else
+               headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
+}
+
+static void convert_str_to_binary(char *b_id_str, uint8_t *b_id_raw)
+{
+       char *c = b_id_str;
+
+       for (int i = 0; i < BUILD_ID_SIZE; i++) {
+               b_id_raw[i] = nibble_to_num(*c) << 4 | nibble_to_num(*(c + 1));
+               c += 2;
+       }
+}
+
+void perfconv_add_kernel_buildid(struct perfconv_context *cctx)
+{
+       struct build_id_event *b_evt;
+       struct mem_block *mb;
+       int ret, fd;
+       char build_id[BUILD_ID_SIZE * 2 + 1] = {0};
+
+       build_id_alloc("[kernel.kallsyms]", &b_evt, &mb);
+       b_evt->header.misc = PERF_RECORD_MISC_KERNEL;
+       b_evt->pid = -1;
+       fd = xopen("#version/build_id", O_RDONLY, 0);
+       ret = read(fd, build_id, sizeof(build_id));
+       if (ret <= 0) {
+               free(mb);
+       } else {
+               convert_str_to_binary(build_id, b_evt->build_id);
+               headers_add_header(&cctx->ph, &cctx->hdrs, HEADER_BUILD_ID, mb);
+       }
+}
+
+/* Helper: adds all the headers, marking them in PH and storing them in
+ * feat_hdrs. */
+static void headers_build(struct perf_header *ph, struct perf_headers *hdrs,
+                          struct mem_file *feat_hdrs)
+{
+       hdr_do_osrelease(ph, hdrs);
+       hdr_do_nrcpus(ph, hdrs);
+       hdr_do_cmdline(ph, hdrs);
+
+       headers_finalize(hdrs, feat_hdrs);
+}
+
 static void perf_header_init(struct perf_header *ph)
 {
        ZERO_DATA(*ph);
@@ -246,22 +520,30 @@ static void perf_header_init(struct perf_header *ph)
        ph->attr_size = sizeof(struct perf_event_attr);
 }
 
-static void emit_attr(struct mem_file *amf, struct mem_file *mmf,
+/* For each attr we emit, we push out the attr, then a perf_file_section for the
+ * id(s) for that attr.  This wasn't mentioned in
+ * https://lwn.net/Articles/644919/, but it's what Linux perf expects
+ * (util/header.c).  It looks like you can have multiple IDs per event attr.
+ * We'll only emit one.  The *contents* of the perf_file_section for the ids
+ * aren't in the attr perf_file_section, they are in a separate one
+ * (attr_id_mf).
+ *
+ * Note that *attr_mf*'s relocs are relative to the base of *attr_id_mf*, which
+ * we'll need to sort out later. */
+static void emit_attr(struct mem_file *attr_mf, struct mem_file *attr_id_mf,
                       const struct perf_event_attr *attr, uint64_t id)
 {
        struct perf_file_section *psids;
        struct perf_file_section sids;
 
-       mem_file_write(amf, attr, sizeof(*attr), 0);
+       mem_file_write(attr_mf, attr, sizeof(*attr), 0);
 
-       sids.offset = mmf->size;
+       sids.offset = attr_id_mf->size;
        sids.size = sizeof(uint64_t);
+       mem_file_write(attr_id_mf, &id, sizeof(uint64_t), 0);
 
-       mem_file_write(mmf, &id, sizeof(uint64_t), 0);
-
-       psids = mem_file_write(amf, &sids, sizeof(sids), MBWR_SOLID);
-
-       mem_file_add_reloc(amf, &psids->offset);
+       psids = mem_file_write(attr_mf, &sids, sizeof(sids), MBWR_SOLID);
+       mem_file_add_reloc(attr_mf, &psids->offset);
 }
 
 /* Given raw_info, which is what the kernel sends as user_data for a particular
@@ -290,15 +572,15 @@ static uint64_t perfconv_get_event_id(struct perfconv_context *cctx,
        attr.sample_period = sel->ev.trigger_count;
        /* Closely coupled with struct perf_record_sample */
        attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
-                          PERF_SAMPLE_ADDR | PERF_SAMPLE_ID | PERF_SAMPLE_CPU |
-                          PERF_SAMPLE_CALLCHAIN;
+                          PERF_SAMPLE_ADDR | PERF_SAMPLE_IDENTIFIER |
+                          PERF_SAMPLE_CPU | PERF_SAMPLE_CALLCHAIN;
        attr.exclude_guest = 1; /* we can't trace VMs yet */
        attr.exclude_hv = 1;    /* we aren't tracing our hypervisor, AFAIK */
        attr.exclude_user = !PMEV_GET_USR(raw_event);
        attr.exclude_kernel = !PMEV_GET_OS(raw_event);
        attr.type = sel->type;
        attr.config = sel->config;
-       emit_attr(&cctx->attrs, &cctx->misc, &attr, raw_info);
+       emit_attr(&cctx->attrs, &cctx->attr_ids, &attr, raw_info);
        sel->attr_emitted = TRUE;
        return raw_info;
 }
@@ -348,9 +630,12 @@ static void emit_pid_mmap64(struct perf_record *pr,
                                                        struct perfconv_context *cctx)
 {
        struct proftype_pid_mmap64 *rec = (struct proftype_pid_mmap64 *) pr->data;
-       size_t size = sizeof(struct perf_record_mmap) + strlen(rec->path) + 1;
+       size_t size = sizeof(struct perf_record_mmap) +
+                     strlen((char*)rec->path) + 1;
        struct perf_record_mmap *xrec = xzmalloc(size);
 
+       hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
+
        xrec->header.type = PERF_RECORD_MMAP;
        xrec->header.misc = PERF_RECORD_MISC_USER;
        xrec->header.size = size;
@@ -358,7 +643,7 @@ static void emit_pid_mmap64(struct perf_record *pr,
        xrec->addr = rec->addr;
        xrec->len = rec->size;
        xrec->pgoff = rec->offset;
-       strcpy(xrec->filename, rec->path);
+       strcpy(xrec->filename, (char*)rec->path);
 
        mem_file_write(&cctx->data, xrec, size, 0);
 
@@ -388,7 +673,7 @@ static void emit_kernel_trace64(struct perf_record *pr,
        xrec->tid = 0;
        xrec->time = rec->tstamp;
        xrec->addr = rec->trace[0];
-       xrec->id = perfconv_get_event_id(cctx, rec->info);
+       xrec->identifier = perfconv_get_event_id(cctx, rec->info);
        xrec->cpu = rec->cpu;
        xrec->nr = rec->num_traces - 1;
        memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
@@ -414,7 +699,7 @@ static void emit_user_trace64(struct perf_record *pr,
        xrec->pid = xrec->tid = rec->pid;
        xrec->time = rec->tstamp;
        xrec->addr = rec->trace[0];
-       xrec->id = perfconv_get_event_id(cctx, rec->info);
+       xrec->identifier = perfconv_get_event_id(cctx, rec->info);
        xrec->cpu = rec->cpu;
        xrec->nr = rec->num_traces - 1;
        memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
@@ -428,57 +713,47 @@ static void emit_new_process(struct perf_record *pr,
                                                         struct perfconv_context *cctx)
 {
        struct proftype_new_process *rec = (struct proftype_new_process *) pr->data;
-       const char *comm = strrchr(rec->path, '/');
+       const char *comm;
 
+       hdr_add_buildid(cctx, (char*)rec->path, rec->pid);
+
+       comm = strrchr((char*)rec->path, '/');
        if (!comm)
-               comm = rec->path;
+               comm = (char*)rec->path;
        else
                comm++;
        emit_comm(rec->pid, comm, cctx);
 }
 
-static void add_event_type(struct mem_file *mf, uint64_t id, const char *name)
-{
-       struct perf_trace_event_type evt;
-
-       ZERO_DATA(evt);
-       evt.event_id = id;
-       strncpy(evt.name, name, sizeof(evt.name));
-       evt.name[sizeof(evt.name) - 1] = 0;
-
-       mem_file_write(mf, &evt, sizeof(evt), 0);
-}
-
 struct perfconv_context *perfconv_create_context(struct perf_context *pctx)
 {
        struct perfconv_context *cctx = xzmalloc(sizeof(struct perfconv_context));
 
        cctx->pctx = pctx;
-       xmem_arena_init(&cctx->ma, 0);
        perf_header_init(&cctx->ph);
        headers_init(&cctx->hdrs);
-       mem_file_init(&cctx->fhdrs, &cctx->ma);
-       mem_file_init(&cctx->misc, &cctx->ma);
-       mem_file_init(&cctx->attrs, &cctx->ma);
-       mem_file_init(&cctx->data, &cctx->ma);
-       mem_file_init(&cctx->event_types, &cctx->ma);
+       mem_file_init(&cctx->fhdrs);
+       mem_file_init(&cctx->attr_ids);
+       mem_file_init(&cctx->attrs);
+       mem_file_init(&cctx->data);
+       /* event_types is ignored in newer versions of perf */
+       mem_file_init(&cctx->event_types);
 
        return cctx;
 }
 
 void perfconv_free_context(struct perfconv_context *cctx)
 {
-       if (cctx) {
-               xmem_arena_destroy(&cctx->ma);
+       if (cctx)
                free(cctx);
-       }
 }
 
 void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
                                                        FILE *output)
 {
        size_t processed_records = 0;
-       uint64_t offset, rel_offset;
+       uint64_t offset;
+       uint64_t attr_ids_off = sizeof(cctx->ph);
        struct perf_record pr;
 
        emit_static_mmaps(cctx);
@@ -511,34 +786,45 @@ void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
                free_record(&pr);
        }
 
-       headers_write(&cctx->hdrs, &cctx->ph, &cctx->fhdrs);
-       offset = sizeof(cctx->ph) + cctx->fhdrs.size + cctx->misc.size;
+       /* Add all of the headers before outputting ph */
+       headers_build(&cctx->ph, &cctx->hdrs, &cctx->fhdrs);
 
-       if (cctx->event_types.size > 0) {
-               cctx->ph.event_types.offset = offset;
-               cctx->ph.event_types.size = cctx->event_types.size;
-               offset += cctx->event_types.size;
-       }
-       if (cctx->attrs.size > 0) {
-               cctx->ph.attrs.offset = offset;
-               cctx->ph.attrs.size = cctx->attrs.size;
-               offset += cctx->attrs.size;
-       }
-       if (cctx->data.size > 0) {
-               cctx->ph.data.offset = offset;
-               cctx->ph.data.size = cctx->data.size;
-               offset += cctx->data.size;
-       }
+       /* attrs, events, and data will come after attr_ids. */
+       offset = sizeof(cctx->ph) + cctx->attr_ids.size;
 
-       xfwrite(&cctx->ph, sizeof(cctx->ph), output);
-       mem_file_sync(&cctx->fhdrs, output, OFFSET_NORELOC);
+       /* These are the perf_file_sections in the main perf header.  We need this
+        * sorted out before we emit the PH. */
+       cctx->ph.event_types.offset = offset;
+       cctx->ph.event_types.size = cctx->event_types.size;
+       offset += cctx->event_types.size;
+
+       cctx->ph.attrs.offset = offset;
+       cctx->ph.attrs.size = cctx->attrs.size;
+       offset += cctx->attrs.size;
 
-       rel_offset = (uint64_t) ftell(output);
-       mem_file_sync(&cctx->misc, output, rel_offset);
+       cctx->ph.data.offset = offset;
+       cctx->ph.data.size = cctx->data.size;
+       offset += cctx->data.size;
 
-       mem_file_sync(&cctx->event_types, output, rel_offset);
-       mem_file_sync(&cctx->attrs, output, rel_offset);
-       mem_file_sync(&cctx->data, output, rel_offset);
+       xfwrite(&cctx->ph, sizeof(cctx->ph), output);
+
+       /* attr_ids comes right after the cctx->ph.  We need to put it before
+        * attrs, since attrs needs to know the offset of the base of attrs_ids for
+        * its relocs. */
+       assert(ftell(output) == attr_ids_off);
+       mem_file_sync(&cctx->attr_ids, output, OFFSET_NORELOC);
+       mem_file_sync(&cctx->event_types, output, OFFSET_NORELOC);
+       /* reloc is based off *attr_ids* base */
+       mem_file_sync(&cctx->attrs, output, attr_ids_off);
+       /* Keep data last, so we can append the feature headers.*/
+       mem_file_sync(&cctx->data, output, OFFSET_NORELOC);
+       /* The feature headers must be right after the data section.  I didn't see
+        * anything in the ABI about this, but Linux's perf has this line:
+        *
+        *              ph->feat_offset  = header->data.offset + header->data.size;
+        */
+       mem_file_sync(&cctx->fhdrs, output,
+                     cctx->ph.data.offset + cctx->ph.data.size);
 
        dbg_print(cctx, 2, stderr, "Conversion succeeded: %lu records converted\n",
                          processed_records);