/**
 * @file parse_dump.c
 * parse a jit dump file
 *
 * @remark Copyright 2007 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Jens Wilke
 * @Modifications Maynard Johnson
 * @Modifications Philippe Elie
 * @Modifications Daniel Hansel
 *
 * Copyright IBM Corporation 2007
 *
 */

#include "opjitconv.h"
#include "jitdump.h"
#include "opd_printf.h"
#include "op_libiberty.h"

#include <string.h>
#include <stdio.h>

/* parse a code load record and add the entry to the jitentry list */
static int parse_code_load(void const * ptr_arg, int size,
			   unsigned long long end_time)
{
	struct jitentry * entry;
	int rc = OP_JIT_CONV_OK;
	char const * ptr = ptr_arg;
	struct jr_code_load const * rec = ptr_arg;
	char const * end;
	size_t padding_count, rec_totalsize;
	end = rec->code_addr ? ptr + size : NULL;

	entry = xcalloc(1, sizeof(struct jitentry));

	// jitentry constructor
	entry->next = NULL;
	ptr += sizeof(*rec);
	/* symbol_name can be malloced so we cast away the constness. */
	entry->symbol_name = (char *)ptr;
	entry->sym_name_malloced = 0;
	ptr += strlen(ptr) + 1;
	entry->code = rec->code_addr ? ptr : NULL;
	entry->vma = rec->vma;
	entry->code_size = rec->code_size;
	entry->section = NULL;
	entry->life_start = rec->timestamp;
	// if nothing else is known the symbol lives till the end of the
	// sampling run, this value may be overwritten by an unload record1
	// later
	entry->life_end = end_time;

	// build list
	entry->next = jitentry_list;
	jitentry_list = entry;

	/* padding bytes are calculated over the complete record
	 * (i.e. header + symbol name + code)
	 */
	rec_totalsize = sizeof(*rec) + strlen(entry->symbol_name) + 1 + entry->code_size;
	padding_count = PADDING_8ALIGNED(rec_totalsize);

	verbprintf(debug, "record0: name=%s, vma=%llx, code_size=%i, "
		   "padding_count=%llu, life_start=%lli, life_end=%lli\n", entry->symbol_name,
		   entry->vma, entry->code_size, (unsigned long long)padding_count, entry->life_start,
		   entry->life_end);
	/* If end == NULL, the dump does not include code, and this sanity
	 * check is skipped.
	 */
	if (end && (ptr + entry->code_size + padding_count != end)) {
		verbprintf(debug, "record total size mismatch\n");
		rc = OP_JIT_CONV_FAIL;
	}
	return rc;
}


/*
 * parse a code unload record. Search for existing record with this code
 * address and fill life_end field with the timestamp. linear search not very
 * efficient. FIXME: inefficient
 */
static void parse_code_unload(void const * ptr, unsigned long long end_time)
{
	struct jr_code_unload const * rec = ptr;
	struct jitentry * entry;

	verbprintf(debug,"record1: vma=%llx, life_end=%lli\n",
		   rec->vma, rec->timestamp);
	/**
	 * Normally we won't get a jr_code_unload with a zero time stamp or
	 * a zero code address. The code address is directly provided by the JVMTI.
	 * The documentation of JVMTI does not say anything about the address value if
	 * it could be zero or not. Therefore it is only a sanity check at the moment.
	 */
	if (rec->timestamp > 0 && rec->vma != 0) {
		for (entry = jitentry_list; entry; entry = entry->next) {
			if (entry->vma == rec->vma &&
			    entry->life_end == end_time) {
				entry->life_end = rec->timestamp;
				verbprintf(debug,"matching record found\n");
				break;
			}
		}
	}
}


/*
 * There is no real parsing here, we just record a pointer to the data,
 * we will interpret on the fly the record when building the bfd file.
 */
static void parse_code_debug_info(void const * ptr, void const * end,
				  unsigned long long end_time)
{
	struct jr_code_debug_info const * rec = ptr;
	struct jitentry_debug_line * debug_line =
		xmalloc(sizeof(struct jitentry_debug_line));

	debug_line->data = rec;
	debug_line->end = end;
	debug_line->life_start = rec->timestamp;
	debug_line->life_end = end_time;

	debug_line->next = jitentry_debug_line_list;
	jitentry_debug_line_list = debug_line;
}


/* parse all entries in the jit dump file and build jitentry_list.
 * the code needs to check always whether there is enough
 * to read remaining. this is because the file may be written to
 * concurrently. */
static int parse_entries(void const * ptr, void const * end,
			 unsigned long long end_time)
{
	int rc = OP_JIT_CONV_OK;
	struct jr_prefix const * rec = ptr;

	while ((void *)rec + sizeof(struct jr_prefix) < end) {
		if (((void *) rec + rec->total_size) > end) {
			verbprintf(debug, "record past end of file\n");
			rc = OP_JIT_CONV_FAIL;
			break;
		}

		switch (rec->id) {
		case JIT_CODE_LOAD:
			if (parse_code_load(rec, rec->total_size, end_time)) {
				rc = OP_JIT_CONV_FAIL;
				break;
			}
			break;

		case JIT_CODE_UNLOAD:
			parse_code_unload(rec, end_time);
			break;

		// end of VM live time, no action
		case JIT_CODE_CLOSE:
			break;

		case JIT_CODE_DEBUG_INFO:
			if (rec->total_size == 0) {
				/* op_write_debug_line_info() ensures to write records with
				 * totalsize > 0.
				 */
				rc = OP_JIT_CONV_FAIL;
				break;
			}

			parse_code_debug_info(rec, end, end_time);
			break;

		default:
			verbprintf(debug, "unknown record type\n");
			rc = OP_JIT_CONV_FAIL;
			break;
		}

		/* advance to next record (incl. possible padding bytes) */
		rec = (void *)rec + rec->total_size;
	}

	return rc;
}


/* parse the jit dump header information 
 * The ptr arg is the address of the pointer to the mmapped
 * file, which we modify below.
 */
static int parse_header(char const ** ptr, char const * end)
{
	int rc = OP_JIT_CONV_OK;
	struct jitheader const * header;

	if (*ptr + sizeof(struct jitheader) >= end) {
		verbprintf(debug,
			   "opjitconv: EOF in jitdump file, no header\n");
		rc = OP_JIT_CONV_FAIL;
		goto out;
	}
	header = (struct jitheader *)*ptr;
	if (header->magic != JITHEADER_MAGIC) {
		verbprintf(debug, "opjitconv: Wrong jitdump file magic\n");
		rc = OP_JIT_CONV_FAIL;
		goto out;
	}
	if (header->version != JITHEADER_VERSION) {
		verbprintf(debug, "opjitconv: Wrong jitdump file version\n");
		rc = OP_JIT_CONV_FAIL;
		goto out;
	}
	if (*ptr + header->totalsize > end) {
		verbprintf(debug, "opjitconv: EOF in jitdump file, not enough "
			   "data for header\n");
		rc = OP_JIT_CONV_FAIL;
		goto out;
	}
	dump_bfd_arch = header->bfd_arch;
	dump_bfd_mach = header->bfd_mach;
	dump_bfd_target_name = header->bfd_target;
	verbprintf(debug, "header: bfd-arch=%i, bfd-mach=%i,"
		   " bfd_target_name=%s\n", dump_bfd_arch, dump_bfd_mach,
		   dump_bfd_target_name);
	*ptr = *ptr + header->totalsize;
out:
	return rc;
}


/* Read in the memory mapped jitdump file.
 * Build up jitentry structure and set global variables.
*/
int parse_all(void const * start, void const * end,
	      unsigned long long end_time)
{
	char const * ptr = start;
	if (!parse_header(&ptr, end))
		return parse_entries(ptr, end, end_time);
	else
		return OP_JIT_CONV_FAIL;
}