text: /*
   * jvmti_agent.c: JVMTI agent interface
   *
   * Adapted from the Oprofile code in opagent.c:
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   *
   * Copyright 2007 OProfile authors
   * Jens Wilke
   * Daniel Hansel
   * Copyright IBM Corporation 2007
   */
  #include <sys/types.h>
  #include <sys/stat.h> /* for mkdir() */
  #include <stdio.h>
  #include <errno.h>
  #include <string.h>
  #include <stdlib.h>
  #include <stdint.h>
  #include <limits.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <time.h>
  #include <sys/mman.h>
  #include <syscall.h> /* for gettid() */
  #include <err.h>
  
  #include "jvmti_agent.h"
  #include "../util/jitdump.h"
  
  #define JIT_LANG "java"
  
  static char jit_path[PATH_MAX];
  static void *marker_addr;
  
  /*
   * padding buffer
   */
  static const char pad_bytes[7];
  
  static inline pid_t gettid(void)
  {
  	return (pid_t)syscall(__NR_gettid);
  }
  
  static int get_e_machine(struct jitheader *hdr)
  {
  	ssize_t sret;
  	char id[16];
  	int fd, ret = -1;
  	int m = -1;
  	struct {
  		uint16_t e_type;
  		uint16_t e_machine;
  	} info;
  
  	fd = open("/proc/self/exe", O_RDONLY);
  	if (fd == -1)
  		return -1;
  
  	sret = read(fd, id, sizeof(id));
  	if (sret != sizeof(id))
  		goto error;
  
  	/* check ELF signature */
  	if (id[0] != 0x7f || id[1] != 'E' || id[2] != 'L' || id[3] != 'F')
  		goto error;
  
  	sret = read(fd, &info, sizeof(info));
  	if (sret != sizeof(info))
  		goto error;
  
  	m = info.e_machine;
  	if (m < 0)
  		m = 0; /* ELF EM_NONE */
  
  	hdr->elf_mach = m;
  	ret = 0;
  error:
  	close(fd);
  	return ret;
  }
  
  static int use_arch_timestamp;
  
  static inline uint64_t
  get_arch_timestamp(void)
  {
  #if defined(__i386__) || defined(__x86_64__)
  	unsigned int low, high;
  
  	asm volatile("rdtsc" : "=a" (low), "=d" (high));
  
  	return low | ((uint64_t)high) << 32;
  #else
  	return 0;
  #endif
  }
  
  #define NSEC_PER_SEC	1000000000
  static int perf_clk_id = CLOCK_MONOTONIC;
  
  static inline uint64_t
  timespec_to_ns(const struct timespec *ts)
  {
          return ((uint64_t) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
  }
  
  static inline uint64_t
  perf_get_timestamp(void)
  {
  	struct timespec ts;
  	int ret;
  
  	if (use_arch_timestamp)
  		return get_arch_timestamp();
  
  	ret = clock_gettime(perf_clk_id, &ts);
  	if (ret)
  		return 0;
  
  	return timespec_to_ns(&ts);
  }
  
  static int
  debug_cache_init(void)
  {
  	char str[32];
  	char *base, *p;
  	struct tm tm;
  	time_t t;
  	int ret;
  
  	time(&t);
  	localtime_r(&t, &tm);
  
  	base = getenv("JITDUMPDIR");
  	if (!base)
  		base = getenv("HOME");
  	if (!base)
  		base = ".";
  
  	strftime(str, sizeof(str), JIT_LANG"-jit-%Y%m%d", &tm);
  
  	snprintf(jit_path, PATH_MAX - 1, "%s/.debug/", base);
  
  	ret = mkdir(jit_path, 0755);
  	if (ret == -1) {
  		if (errno != EEXIST) {
  			warn("jvmti: cannot create jit cache dir %s", jit_path);
  			return -1;
  		}
  	}
  
  	snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit", base);
  	ret = mkdir(jit_path, 0755);
  	if (ret == -1) {
  		if (errno != EEXIST) {
  			warn("cannot create jit cache dir %s", jit_path);
  			return -1;
  		}
  	}
  
  	snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit/%s.XXXXXXXX", base, str);
  
  	p = mkdtemp(jit_path);
  	if (p != jit_path) {
  		warn("cannot create jit cache dir %s", jit_path);
  		return -1;
  	}
  
  	return 0;
  }
  
  static int
  perf_open_marker_file(int fd)
  {
  	long pgsz;
  
  	pgsz = sysconf(_SC_PAGESIZE);
  	if (pgsz == -1)
  		return -1;
  
  	/*
  	 * we mmap the jitdump to create an MMAP RECORD in perf.data file.
  	 * The mmap is captured either live (perf record running when we mmap)
  	 * or  in deferred mode, via /proc/PID/maps
  	 * the MMAP record is used as a marker of a jitdump file for more meta
  	 * data info about the jitted code. Perf report/annotate detect this
  	 * special filename and process the jitdump file.
  	 *
  	 * mapping must be PROT_EXEC to ensure it is captured by perf record
  	 * even when not using -d option
  	 */
  	marker_addr = mmap(NULL, pgsz, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
  	return (marker_addr == MAP_FAILED) ? -1 : 0;
  }
  
  static void
  perf_close_marker_file(void)
  {
  	long pgsz;
  
  	if (!marker_addr)
  		return;
  
  	pgsz = sysconf(_SC_PAGESIZE);
  	if (pgsz == -1)
  		return;
  
  	munmap(marker_addr, pgsz);
  }
  
  static void
  init_arch_timestamp(void)
  {
  	char *str = getenv("JITDUMP_USE_ARCH_TIMESTAMP");
  
  	if (!str || !*str || !strcmp(str, "0"))
  		return;
  
  	use_arch_timestamp = 1;
  }
  
  void *jvmti_open(void)
  {
  	int pad_cnt;
  	char dump_path[PATH_MAX];
  	struct jitheader header;
  	int fd;
  	FILE *fp;
  
  	init_arch_timestamp();
  
  	/*
  	 * check if clockid is supported
  	 */
  	if (!perf_get_timestamp()) {
  		if (use_arch_timestamp)
  			warnx("jvmti: arch timestamp not supported");
  		else
  			warnx("jvmti: kernel does not support %d clock id", perf_clk_id);
  	}
  
  	memset(&header, 0, sizeof(header));
  
  	debug_cache_init();
  
  	/*
  	 * jitdump file name
  	 */
  	snprintf(dump_path, PATH_MAX, "%s/jit-%i.dump", jit_path, getpid());
  
  	fd = open(dump_path, O_CREAT|O_TRUNC|O_RDWR, 0666);
  	if (fd == -1)
  		return NULL;
  
  	/*
  	 * create perf.data maker for the jitdump file
  	 */
  	if (perf_open_marker_file(fd)) {
  		warnx("jvmti: failed to create marker file");
  		return NULL;
  	}
  
  	fp = fdopen(fd, "w+");
  	if (!fp) {
  		warn("jvmti: cannot create %s", dump_path);
  		close(fd);
  		goto error;
  	}
  
  	warnx("jvmti: jitdump in %s", dump_path);
  
  	if (get_e_machine(&header)) {
  		warn("get_e_machine failed\n");
  		goto error;
  	}
  
  	header.magic      = JITHEADER_MAGIC;
  	header.version    = JITHEADER_VERSION;
  	header.total_size = sizeof(header);
  	header.pid        = getpid();
  
  	/* calculate amount of padding '\0' */
  	pad_cnt = PADDING_8ALIGNED(header.total_size);
  	header.total_size += pad_cnt;
  
  	header.timestamp = perf_get_timestamp();
  
  	if (use_arch_timestamp)
  		header.flags |= JITDUMP_FLAGS_ARCH_TIMESTAMP;
  
  	if (!fwrite(&header, sizeof(header), 1, fp)) {
  		warn("jvmti: cannot write dumpfile header");
  		goto error;
  	}
  
  	/* write padding '\0' if necessary */
  	if (pad_cnt && !fwrite(pad_bytes, pad_cnt, 1, fp)) {
  		warn("jvmti: cannot write dumpfile header padding");
  		goto error;
  	}
  
  	return fp;
  error:
  	fclose(fp);
  	return NULL;
  }
  
  int
  jvmti_close(void *agent)
  {
  	struct jr_code_close rec;
  	FILE *fp = agent;
  
  	if (!fp) {
  		warnx("jvmti: incalid fd in close_agent");
  		return -1;
  	}
  
  	rec.p.id = JIT_CODE_CLOSE;
  	rec.p.total_size = sizeof(rec);
  
  	rec.p.timestamp = perf_get_timestamp();
  
  	if (!fwrite(&rec, sizeof(rec), 1, fp))
  		return -1;
  
  	fclose(fp);
  
  	fp = NULL;
  
  	perf_close_marker_file();
  
  	return 0;
  }
  
  int
  jvmti_write_code(void *agent, char const *sym,
  	uint64_t vma, void const *code, unsigned int const size)
  {
  	static int code_generation = 1;
  	struct jr_code_load rec;
  	size_t sym_len;
  	size_t padding_count;
  	FILE *fp = agent;
  	int ret = -1;
  
  	/* don't care about 0 length function, no samples */
  	if (size == 0)
  		return 0;
  
  	if (!fp) {
  		warnx("jvmti: invalid fd in write_native_code");
  		return -1;
  	}
  
  	sym_len = strlen(sym) + 1;
  
  	rec.p.id           = JIT_CODE_LOAD;
  	rec.p.total_size   = sizeof(rec) + sym_len;
  	padding_count      = PADDING_8ALIGNED(rec.p.total_size);
  	rec.p. total_size += padding_count;
  	rec.p.timestamp    = perf_get_timestamp();
  
  	rec.code_size  = size;
  	rec.vma        = vma;
  	rec.code_addr  = vma;
  	rec.pid	       = getpid();
  	rec.tid	       = gettid();
  
  	if (code)
  		rec.p.total_size += size;
  
  	/*
  	 * If JVM is multi-threaded, nultiple concurrent calls to agent
  	 * may be possible, so protect file writes
  	 */
  	flockfile(fp);
  
  	/*
  	 * get code index inside lock to avoid race condition
  	 */
  	rec.code_index = code_generation++;
  
  	ret = fwrite_unlocked(&rec, sizeof(rec), 1, fp);
  	fwrite_unlocked(sym, sym_len, 1, fp);
  
  	if (padding_count)
  		fwrite_unlocked(pad_bytes, padding_count, 1, fp);
  
  	if (code)
  		fwrite_unlocked(code, size, 1, fp);
  
  	funlockfile(fp);
  
  	ret = 0;
  
  	return ret;
  }
  
  int
  jvmti_write_debug_info(void *agent, uint64_t code, const char *file,
  		       jvmti_line_info_t *li, int nr_lines)
  {
  	struct jr_code_debug_info rec;
  	size_t sret, len, size, flen;
  	size_t padding_count;
  	uint64_t addr;
  	const char *fn = file;
  	FILE *fp = agent;
  	int i;
  
  	/*
  	 * no entry to write
  	 */
  	if (!nr_lines)
  		return 0;
  
  	if (!fp) {
  		warnx("jvmti: invalid fd in write_debug_info");
  		return -1;
  	}
  
  	flen = strlen(file) + 1;
  
  	rec.p.id        = JIT_CODE_DEBUG_INFO;
  	size            = sizeof(rec);
  	rec.p.timestamp = perf_get_timestamp();
  	rec.code_addr   = (uint64_t)(uintptr_t)code;
  	rec.nr_entry    = nr_lines;
  
  	/*
  	 * on disk source line info layout:
  	 * uint64_t : addr
  	 * int      : line number
  	 * int      : column discriminator
  	 * file[]   : source file name
  	 * padding  : pad to multiple of 8 bytes
  	 */
  	size += nr_lines * sizeof(struct debug_entry);
  	size += flen * nr_lines;
  	/*
  	 * pad to 8 bytes
  	 */
  	padding_count = PADDING_8ALIGNED(size);
  
  	rec.p.total_size = size + padding_count;
  
  	/*
  	 * If JVM is multi-threaded, nultiple concurrent calls to agent
  	 * may be possible, so protect file writes
  	 */
  	flockfile(fp);
  
  	sret = fwrite_unlocked(&rec, sizeof(rec), 1, fp);
  	if (sret != 1)
  		goto error;
  
  	for (i = 0; i < nr_lines; i++) {
  
  		addr = (uint64_t)li[i].pc;
  		len  = sizeof(addr);
  		sret = fwrite_unlocked(&addr, len, 1, fp);
  		if (sret != 1)
  			goto error;
  
  		len  = sizeof(li[0].line_number);
  		sret = fwrite_unlocked(&li[i].line_number, len, 1, fp);
  		if (sret != 1)
  			goto error;
  
  		len  = sizeof(li[0].discrim);
  		sret = fwrite_unlocked(&li[i].discrim, len, 1, fp);
  		if (sret != 1)
  			goto error;
  
  		sret = fwrite_unlocked(fn, flen, 1, fp);
  		if (sret != 1)
  			goto error;
  	}
  	if (padding_count)
  		sret = fwrite_unlocked(pad_bytes, padding_count, 1, fp);
  		if (sret != 1)
  			goto error;
  
  	funlockfile(fp);
  	return 0;
  error:
  	funlockfile(fp);
  	return -1;
  }
  