/**
 * @file libutil++/op_spu_bfd.cpp
 * Encapsulation of bfd objects for Cell BE SPU
 *
 * @remark Copyright 2007 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Maynard Johnson
 * (C) Copyright IBM Corporation 2007
 */


#include <fcntl.h>
#include <sys/stat.h>
#include <cstdlib>
#include <cstring>

#include <iostream>
#include <cstring>
#include <cstdlib>

#include "op_bfd.h"
#include "locate_images.h"
#include "op_libiberty.h"
#include "string_filter.h"
#include "cverb.h"

#define OP_SPU_DYN_FLAG		0x10000000	/* kernel module adds this offset */
						/* to SPU code it can't find in the map */
#define OP_SPU_MEMSIZE		0x3ffff		/* Physical memory size on an SPU */

using namespace std;

extern verbose vbfd;

/*
 * This overload of the op_bfd constructor is patterned after the
 * constructor in libutil++/op_bfd.cpp, with the additional processing
 * needed to handle an embedded spu offset.
 */
op_bfd::op_bfd(uint64_t spu_offset, string const & fname,
	       string_filter const & symbol_filter, 
	       extra_images const & extra_images, bool & ok)
	:
	archive_path(extra_images.get_archive_path()),
	extra_found_images(extra_images),
	file_size(-1),
	embedding_filename(fname),
	anon_obj(false)
{
	int fd;
	struct stat st;
	int notes_remaining;
	bool spu_note_found = false;
	size_t sec_size = 0;
	unsigned int oct_per_byte;
	asection * note = NULL;

	symbols_found_t symbols;
	asection const * sect;

	image_error image_ok;
	string const image_path =
		extra_images.find_image_path(fname, image_ok, true);

	cverb << vbfd << "op_bfd ctor for " << image_path << endl;
	if (!ok)
		goto out_fail;

	fd = open(image_path.c_str(), O_RDONLY);
	if (fd == -1) {
		cverb << vbfd << "open failed for " << image_path << endl;
		ok = false;
		goto out_fail;
	}

	if (fstat(fd, &st)) {
		cverb << vbfd << "stat failed for " << image_path << endl;
		ok = false;
		goto out_fail;
	}

	file_size = st.st_size;
	ibfd.abfd = spu_open_bfd(image_path, fd, spu_offset);

	if (!ibfd.valid()) {
		cverb << vbfd << "fdopen_bfd failed for " << image_path << endl;
		ok = false;
		goto out_fail;
	}

	/* For embedded SPU ELF, a note section named '.note.spu_name'
	 * contains the name of the SPU binary image in the description
	 * field.
	 */
	note = bfd_get_section_by_name(ibfd.abfd, ".note.spu_name");
	if (!note) {
		cverb << vbfd << "No .note.spu-name section found" << endl;
		goto find_sec_code;
	}
	cverb << vbfd << "found .note.spu_name section" << endl;

	bfd_byte * sec_contents;
	oct_per_byte = bfd_octets_per_byte(ibfd.abfd);
	sec_size = bfd_section_size(ibfd.abfd, note)/oct_per_byte;

	sec_contents = (bfd_byte *) xmalloc(sec_size);
	if (!bfd_get_section_contents(ibfd.abfd, note, sec_contents,
				      0, sec_size)) {
		cverb << vbfd << "bfd_get_section_contents with size "
		      << sec_size << " returned an error" << endl;
		ok = false;
		goto out_fail;
	}
	notes_remaining = sec_size;
	while (notes_remaining && !spu_note_found) {
		unsigned int  nsize, dsize, type;
		nsize = *((unsigned int *) sec_contents);
		dsize = *((unsigned int *) sec_contents +1);
		type = *((unsigned int *) sec_contents +2);
		int remainder, desc_start, name_pad_length, desc_pad_length;
		name_pad_length = desc_pad_length = 0;
		/* Calculate padding for 4-byte alignment */
		remainder = nsize % 4;
		if (remainder != 0)
			name_pad_length = 4 - remainder;
		desc_start = 12 + nsize + name_pad_length;
		if (type != 1) {
			int note_record_length;
			if ((remainder = (dsize % 4)) != 0)
				desc_pad_length = 4 - remainder;
			note_record_length = 12 + nsize +
				name_pad_length + dsize + desc_pad_length;
			notes_remaining -= note_record_length;
			sec_contents += note_record_length;
			continue;
		} else {
			spu_note_found = true;
			/* Must memcpy the data from sec_contents to a
			 * 'char *' first, then stringify it, since
			 * the type of sec_contents (bfd_byte *) cannot be
			 * used as input for creating a string.
			 */
			char * description = (char *) xmalloc(dsize);
			memcpy(description, sec_contents + desc_start, dsize);
			filename = description;
			free(description);
		}
	}
	free(sec_contents);
	/* Default to app name for the image name */
	if (spu_note_found == false)
		filename = fname;

find_sec_code:
	for (sect = ibfd.abfd->sections; sect; sect = sect->next) {
		if (sect->flags & SEC_CODE) {
			if (filepos_map[sect->name] != 0) {
				cerr << "Found section \"" << sect->name
				     << "\" twice for " << get_filename()
				     << endl;
				abort();
			}

			filepos_map[sect->name] = sect->filepos;
		}
	}

	get_symbols(symbols);

	/* In some cases the SPU library code generates code stubs on the stack. */
	/* The kernel module remaps those addresses so add an entry to catch/report them. */
	symbols.push_back(op_bfd_symbol(OP_SPU_DYN_FLAG, OP_SPU_MEMSIZE,
			  "__send_to_ppe(stack)"));

out:
	add_symbols(symbols, symbol_filter);
	return;
out_fail:
	ibfd.close();
	dbfd.close();
	file_size = -1;
	goto out;
}