| /* 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 | 
 |  * format has bee illustrated here: | 
 |  * | 
 |  *	 https://lwn.net/Articles/644919/ | 
 |  *	 https://openlab-mu-internal.web.cern.ch/openlab-mu-internal/03_Documents/ | 
 |  *			 3_Technical_Documents/Technical_Reports/2011/Urs_Fassler_report.pdf | 
 |  * | 
 |  */ | 
 |  | 
 | #include <ros/common.h> | 
 | #include <ros/memops.h> | 
 | #include <ros/profiler_records.h> | 
 | #include <sys/types.h> | 
 | #include <sys/stat.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <stdint.h> | 
 | #include <unistd.h> | 
 | #include <string.h> | 
 | #include <errno.h> | 
 | #include <stdarg.h> | 
 | #include <assert.h> | 
 | #include "perf_format.h" | 
 | #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 | 
 | #define OFFSET_NORELOC ((uint64_t) -1) | 
 | #define MEMFILE_BLOCK_SIZE (64 * 1024) | 
 |  | 
 | #define MBWR_SOLID (1 << 0) | 
 |  | 
 | char *cmd_line_save; | 
 |  | 
 | struct perf_record { | 
 | 	uint64_t type; | 
 | 	uint64_t size; | 
 | 	char *data; | 
 | 	char buffer[PERF_RECORD_BUFFER_SIZE]; | 
 | }; | 
 |  | 
 | static void dbg_print(struct perfconv_context *cctx, int level, FILE *file, | 
 | 					  const char *fmt, ...) | 
 | { | 
 | 	if (cctx->debug_level >= level) { | 
 | 		va_list args; | 
 |  | 
 | 		va_start(args, fmt); | 
 | 		vfprintf(file, fmt, args); | 
 | 		va_end(args); | 
 | 	} | 
 | } | 
 |  | 
 | void perfconv_set_dbglevel(int level, struct perfconv_context *cctx) | 
 | { | 
 | 	cctx->debug_level = level; | 
 | } | 
 |  | 
 | static void free_record(struct perf_record *pr) | 
 | { | 
 | 	if (pr->data != pr->buffer) | 
 | 		free(pr->data); | 
 | 	pr->data = NULL; | 
 | } | 
 |  | 
 | static int read_record(FILE *file, struct perf_record *pr) | 
 | { | 
 | 	if (vb_fdecode_uint64(file, &pr->type) == EOF || | 
 | 		vb_fdecode_uint64(file, &pr->size) == EOF) | 
 | 		return EOF; | 
 | 	if (pr->size > MAX_PERF_RECORD_SIZE) { | 
 | 		fprintf(stderr, "Invalid record size: type=%lu size=%lu\n", pr->type, | 
 | 				pr->size); | 
 | 		exit(1); | 
 | 	} | 
 | 	if (pr->size > sizeof(pr->buffer)) | 
 | 		pr->data = xmalloc((size_t) pr->size); | 
 | 	else | 
 | 		pr->data = pr->buffer; | 
 | 	if (fread(pr->data, 1, (size_t) pr->size, file) != (size_t) pr->size) { | 
 | 		fprintf(stderr, "Unable to read record memory: size=%lu\n", | 
 | 				pr->size); | 
 | 		return EOF; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct mem_block *mem_block_alloc(size_t size) | 
 | { | 
 | 	struct mem_block *mb = xmalloc(sizeof(struct mem_block) + size); | 
 |  | 
 | 	mb->next = NULL; | 
 | 	mb->base = mb->wptr = (char *) mb + sizeof(struct mem_block); | 
 | 	mb->top = mb->base + size; | 
 |  | 
 | 	return mb; | 
 | } | 
 |  | 
 | static char *mem_block_write(struct mem_block *mb, const void *data, | 
 | 							 size_t size) | 
 | { | 
 | 	char *wrbase = mb->wptr; | 
 |  | 
 | 	always_assert(size <= mb->top - mb->wptr); | 
 |  | 
 | 	memcpy(mb->wptr, data, size); | 
 | 	mb->wptr += size; | 
 |  | 
 | 	return wrbase; | 
 | } | 
 |  | 
 | static void mem_file_init(struct mem_file *mf) | 
 | { | 
 | 	ZERO_DATA(*mf); | 
 | } | 
 |  | 
 | static int mem_block_can_write(struct mem_block *mb, size_t size, int flags) | 
 | { | 
 | 	size_t space = mb->top - mb->wptr; | 
 |  | 
 | 	return (flags & MBWR_SOLID) ? (space >= size) : (space > 0); | 
 | } | 
 |  | 
 | static void *mem_file_write(struct mem_file *mf, const void *data, size_t size, | 
 | 							int flags) | 
 | { | 
 | 	void *wrbase = NULL; | 
 |  | 
 | 	while (size > 0) { | 
 | 		size_t space, csize; | 
 | 		struct mem_block *mb = mf->tail; | 
 |  | 
 | 		if (!mb || !mem_block_can_write(mb, size, flags)) { | 
 | 			mb = mem_block_alloc(max(MEMFILE_BLOCK_SIZE, size)); | 
 | 			if (!mf->tail) | 
 | 				mf->head = mb; | 
 | 			else | 
 | 				mf->tail->next = mb; | 
 | 			mf->tail = mb; | 
 | 		} | 
 | 		space = mb->top - mb->wptr; | 
 | 		csize = min(size, space); | 
 |  | 
 | 		wrbase = mem_block_write(mb, data, csize); | 
 | 		mf->size += csize; | 
 |  | 
 | 		size -= csize; | 
 | 		data = (const char *) data + csize; | 
 | 	} | 
 |  | 
 | 	return wrbase; | 
 | } | 
 |  | 
 | static void mem_file_sync(struct mem_file *mf, FILE *file, uint64_t rel_offset) | 
 | { | 
 | 	struct mem_block *mb; | 
 |  | 
 | 	if (rel_offset != 0) { | 
 | 		struct mem_file_reloc *rel; | 
 |  | 
 | 		always_assert(!mf->relocs || rel_offset != OFFSET_NORELOC); | 
 |  | 
 | 		for (rel = mf->relocs; rel; rel = rel->next) | 
 | 			*rel->ptr += rel_offset; | 
 | 	} | 
 |  | 
 | 	for (mb = mf->head; mb; mb = mb->next) | 
 | 		xfwrite(mb->base, mb->wptr - mb->base, file); | 
 | } | 
 |  | 
 | static struct mem_file_reloc *mem_file_add_reloc(struct mem_file *mf, | 
 | 												 uint64_t *ptr) | 
 | { | 
 | 	struct mem_file_reloc *rel = xzmalloc(sizeof(struct mem_file_reloc)); | 
 |  | 
 | 	rel->ptr = ptr; | 
 | 	rel->next = mf->relocs; | 
 | 	mf->relocs = rel; | 
 |  | 
 | 	return rel; | 
 | } | 
 |  | 
 | void perfconv_add_kernel_mmap(struct perfconv_context *cctx) | 
 | { | 
 | 	char path[] = "[kernel.kallsyms]"; | 
 | 	struct static_mmap64 *mm; | 
 |  | 
 | 	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; | 
 | 	/* Linux sets addr = 0, size = 0xffffffff9fffffff, off = 0xffffffff81000000 | 
 | 	 * Their mmap record is also called [kernel.kallsyms]_text (I think).  They | 
 | 	 * also have a _text symbol in kallsyms at ffffffff81000000 (equiv to our | 
 | 	 * KERN_LOAD_ADDR (which is 0xffffffffc0000000)).  Either way, this seems to | 
 | 	 * work for us; we'll see.  It's also arch-independent (for now). */ | 
 | 	mm->addr = 0; | 
 | 	mm->size = 0xffffffffffffffff; | 
 | 	mm->offset = 0x0; | 
 | 	mm->path = xstrdup(path); | 
 |  | 
 | 	mm->next = cctx->static_mmaps; | 
 | 	cctx->static_mmaps = mm; | 
 | } | 
 |  | 
 | static void headers_init(struct perf_headers *hdrs) | 
 | { | 
 | 	ZERO_DATA(*hdrs); | 
 | } | 
 |  | 
 | /* 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); | 
 | } | 
 |  | 
 | /* Emits the headers contents to the mem_file */ | 
 | static void headers_finalize(struct perf_headers *hdrs, struct mem_file *mf) | 
 | { | 
 | 	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); | 
 |  | 
 | 	/* 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); | 
 | 			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); | 
 | 	ph->magic = PERF_MAGIC2; | 
 | 	ph->size = sizeof(*ph); | 
 | 	ph->attr_size = sizeof(struct perf_event_attr); | 
 | } | 
 |  | 
 | /* 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(attr_mf, attr, sizeof(*attr), 0); | 
 |  | 
 | 	sids.offset = attr_id_mf->size; | 
 | 	sids.size = sizeof(uint64_t); | 
 | 	mem_file_write(attr_id_mf, &id, sizeof(uint64_t), 0); | 
 |  | 
 | 	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 | 
 |  * sample, look up the 'id' for the event/sample.  The 'id' identifies the event | 
 |  * stream that the sample is a part of.  There are many samples per event | 
 |  * stream, all identified by 'id.'  It doesn't matter what 'id', so long as it | 
 |  * is unique.  We happen to use the pointer to the sample's eventsel. | 
 |  * | 
 |  * If this is the first time we've seen 'raw_info', we'll also add an attribute | 
 |  * to the perf ctx.  There is one attr per 'id' / event stream. */ | 
 | static uint64_t perfconv_get_event_id(struct perfconv_context *cctx, | 
 | 									  uint64_t raw_info) | 
 | { | 
 | 	struct perf_eventsel *sel = (struct perf_eventsel*)raw_info; | 
 | 	struct perf_event_attr attr; | 
 | 	uint64_t raw_event; | 
 |  | 
 | 	assert(sel); | 
 | 	if (sel->attr_emitted) | 
 | 		return raw_info; | 
 | 	raw_event = sel->ev.event; | 
 | 	ZERO_DATA(attr); | 
 | 	attr.size = sizeof(attr); | 
 | 	attr.mmap = 1; | 
 | 	attr.comm = 1; | 
 | 	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_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->attr_ids, &attr, raw_info); | 
 | 	sel->attr_emitted = TRUE; | 
 | 	return raw_info; | 
 | } | 
 |  | 
 | static void emit_static_mmaps(struct perfconv_context *cctx) | 
 | { | 
 | 	struct static_mmap64 *mm; | 
 |  | 
 | 	for (mm = cctx->static_mmaps; mm; mm = mm->next) { | 
 | 		size_t size = sizeof(struct perf_record_mmap) + strlen(mm->path) + 1; | 
 | 		struct perf_record_mmap *xrec = xzmalloc(size); | 
 |  | 
 | 		xrec->header.type = PERF_RECORD_MMAP; | 
 | 		xrec->header.misc = mm->header_misc; | 
 | 		xrec->header.size = size; | 
 | 		xrec->pid = mm->pid; | 
 | 		xrec->tid = mm->tid; | 
 | 		xrec->addr = mm->addr; | 
 | 		xrec->len = mm->size; | 
 | 		xrec->pgoff = mm->offset; | 
 | 		strcpy(xrec->filename, mm->path); | 
 |  | 
 | 		mem_file_write(&cctx->data, xrec, size, 0); | 
 |  | 
 | 		free(xrec); | 
 | 	} | 
 | } | 
 |  | 
 | static void emit_comm(uint32_t pid, const char *comm, | 
 | 					  struct perfconv_context *cctx) | 
 | { | 
 | 	size_t size = sizeof(struct perf_record_comm) + strlen(comm) + 1; | 
 | 	struct perf_record_comm *xrec = xzmalloc(size); | 
 |  | 
 | 	xrec->header.type = PERF_RECORD_COMM; | 
 | 	xrec->header.misc = PERF_RECORD_MISC_USER; | 
 | 	xrec->header.size = size; | 
 | 	xrec->pid = xrec->tid = pid; | 
 | 	strcpy(xrec->comm, comm); | 
 |  | 
 | 	mem_file_write(&cctx->data, xrec, size, 0); | 
 |  | 
 | 	free(xrec); | 
 | } | 
 |  | 
 | 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((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; | 
 | 	xrec->pid = xrec->tid = rec->pid; | 
 | 	xrec->addr = rec->addr; | 
 | 	xrec->len = rec->size; | 
 | 	xrec->pgoff = rec->offset; | 
 | 	strcpy(xrec->filename, (char*)rec->path); | 
 |  | 
 | 	mem_file_write(&cctx->data, xrec, size, 0); | 
 |  | 
 | 	free(xrec); | 
 | } | 
 |  | 
 | static void emit_kernel_trace64(struct perf_record *pr, | 
 | 								struct perfconv_context *cctx) | 
 | { | 
 | 	struct proftype_kern_trace64 *rec = (struct proftype_kern_trace64 *) | 
 | 		pr->data; | 
 | 	size_t size = sizeof(struct perf_record_sample) + | 
 | 		(rec->num_traces - 1) * sizeof(uint64_t); | 
 | 	struct perf_record_sample *xrec = xzmalloc(size); | 
 |  | 
 | 	xrec->header.type = PERF_RECORD_SAMPLE; | 
 | 	xrec->header.misc = PERF_RECORD_MISC_KERNEL; | 
 | 	xrec->header.size = size; | 
 | 	xrec->ip = rec->trace[0]; | 
 | 	/* TODO: -1 means "not a process".  We could track ktasks with IDs, emit | 
 | 	 * COMM events for them (probably!) and report them as the tid.  For now, | 
 | 	 * tid of 0 means [swapper] to Linux. */ | 
 | 	if (rec->pid == -1) { | 
 | 		xrec->pid = -1; | 
 | 		xrec->tid = 0; | 
 | 	} else { | 
 | 		xrec->pid = rec->pid; | 
 | 		xrec->tid = rec->pid; | 
 | 	} | 
 | 	xrec->time = rec->tstamp; | 
 | 	xrec->addr = rec->trace[0]; | 
 | 	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)); | 
 |  | 
 | 	mem_file_write(&cctx->data, xrec, size, 0); | 
 |  | 
 | 	free(xrec); | 
 | } | 
 |  | 
 | static void emit_user_trace64(struct perf_record *pr, | 
 | 							  struct perfconv_context *cctx) | 
 | { | 
 | 	struct proftype_user_trace64 *rec = (struct proftype_user_trace64 *) | 
 | 		pr->data; | 
 | 	size_t size = sizeof(struct perf_record_sample) + | 
 | 		(rec->num_traces - 1) * sizeof(uint64_t); | 
 | 	struct perf_record_sample *xrec = xzmalloc(size); | 
 |  | 
 | 	xrec->header.type = PERF_RECORD_SAMPLE; | 
 | 	xrec->header.misc = PERF_RECORD_MISC_USER; | 
 | 	xrec->header.size = size; | 
 | 	xrec->ip = rec->trace[0]; | 
 | 	xrec->pid = xrec->tid = rec->pid; | 
 | 	xrec->time = rec->tstamp; | 
 | 	xrec->addr = rec->trace[0]; | 
 | 	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)); | 
 |  | 
 | 	mem_file_write(&cctx->data, xrec, size, 0); | 
 |  | 
 | 	free(xrec); | 
 | } | 
 |  | 
 | 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; | 
 |  | 
 | 	hdr_add_buildid(cctx, (char*)rec->path, rec->pid); | 
 |  | 
 | 	comm = strrchr((char*)rec->path, '/'); | 
 | 	if (!comm) | 
 | 		comm = (char*)rec->path; | 
 | 	else | 
 | 		comm++; | 
 | 	emit_comm(rec->pid, comm, cctx); | 
 | } | 
 |  | 
 | struct perfconv_context *perfconv_create_context(struct perf_context *pctx) | 
 | { | 
 | 	struct perfconv_context *cctx = xzmalloc(sizeof(struct perfconv_context)); | 
 |  | 
 | 	cctx->pctx = pctx; | 
 | 	perf_header_init(&cctx->ph); | 
 | 	headers_init(&cctx->hdrs); | 
 | 	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) | 
 | 		free(cctx); | 
 | } | 
 |  | 
 | void perfconv_process_input(struct perfconv_context *cctx, FILE *input, | 
 | 							FILE *output) | 
 | { | 
 | 	size_t processed_records = 0; | 
 | 	uint64_t offset; | 
 | 	uint64_t attr_ids_off = sizeof(cctx->ph); | 
 | 	struct perf_record pr; | 
 |  | 
 | 	emit_static_mmaps(cctx); | 
 |  | 
 | 	while (read_record(input, &pr) == 0) { | 
 | 		dbg_print(cctx, 8, stderr, "Valid record: type=%lu size=%lu\n", | 
 | 				  pr.type, pr.size); | 
 |  | 
 | 		processed_records++; | 
 |  | 
 | 		switch (pr.type) { | 
 | 		case PROFTYPE_KERN_TRACE64: | 
 | 			emit_kernel_trace64(&pr, cctx); | 
 | 			break; | 
 | 		case PROFTYPE_USER_TRACE64: | 
 | 			emit_user_trace64(&pr, cctx); | 
 | 			break; | 
 | 		case PROFTYPE_PID_MMAP64: | 
 | 			emit_pid_mmap64(&pr, cctx); | 
 | 			break; | 
 | 		case PROFTYPE_NEW_PROCESS: | 
 | 			emit_new_process(&pr, cctx); | 
 | 			break; | 
 | 		default: | 
 | 			fprintf(stderr, "Unknown record: type=%lu size=%lu\n", pr.type, | 
 | 					pr.size); | 
 | 			processed_records--; | 
 | 		} | 
 |  | 
 | 		free_record(&pr); | 
 | 	} | 
 |  | 
 | 	/* Add all of the headers before outputting ph */ | 
 | 	headers_build(&cctx->ph, &cctx->hdrs, &cctx->fhdrs); | 
 |  | 
 | 	/* attrs, events, and data will come after attr_ids. */ | 
 | 	offset = sizeof(cctx->ph) + cctx->attr_ids.size; | 
 |  | 
 | 	/* 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; | 
 |  | 
 | 	cctx->ph.data.offset = offset; | 
 | 	cctx->ph.data.size = cctx->data.size; | 
 | 	offset += cctx->data.size; | 
 |  | 
 | 	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); | 
 | } |