C++程序  |  390行  |  10.19 KB

/**
 * @file opagent.c
 * Interface to report symbol names and dynamically generated code to Oprofile
 *
 * @remark Copyright 2007 OProfile authors
 *
 * 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
 *
 * @author Jens Wilke
 * @Modifications Daniel Hansel
 *
 * Copyright IBM Corporation 2007
 *
 */

/******************************************************************
 * ATTENTION: 
 *   When adding new functions to this interface, you MUST update
 *   opagent_symbols.ver.
 *
 *   If a change is made to an existing exported function, perform the
 *   the following steps.  As an example, assume op_open_agent()
 *   is being updated to include a 'dump_code' parameter. 
 *     1. Update the opagent.ver file with a new version node, and
 *        add the op_open_agent to it.  Note that op_open_agent
 *        is also still declared in the original version node.
 *     2. Add '__asm__(".symver <blah>") directives to this .c source file.
 *        For this example, the directives would be as follows:
 *            __asm__(".symver op_open_agent_1_0,op_open_agent@OPAGENT_1.0");
 *            __asm__(".symver op_open_agent_2_0,op_open_agent@@OPAGENT_2.0");
 *     3. Update the declaration of op_open_agent in the header file with
 *        the additional parameter.
 *     4. Change the name of the original op_open_agent to "op_open_agent_1_0"
 *        in this .c source file.
 *     5. Add the new op_open_agent_2_0(int dump_code) function in this
 *        .c source file.
 *            
 *   See libopagent/Makefile.am for more information.
 *******************************************************************/

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <bfd.h>

#include "opagent.h"
#include "op_config.h"
#include "jitdump.h"

// Declare BFD-related global variables.
static char * _bfd_target_name;
static int _bfd_arch;
static unsigned int _bfd_mach;

// Define BFD-related global variables.
static int define_bfd_vars(void)
{
	bfd * bfd;
	bfd_boolean r;
	int len;
#define MAX_PATHLENGTH 2048
	char mypath[MAX_PATHLENGTH];
     
	len = readlink("/proc/self/exe", mypath, sizeof(mypath));
     
	if (len < 0) {
		fprintf(stderr, "libopagent: readlink /proc/self/exe failed\n");
		return -1;
	}
	if (len >= MAX_PATHLENGTH) {
		fprintf(stderr, "libopagent: readlink /proc/self/exe returned"
			" path length longer than %d.\n", MAX_PATHLENGTH);

		return -1;
	}
	mypath[len] = '\0';

	bfd_init();
	bfd = bfd_openr(mypath, NULL);
	if (bfd == NULL) {
		bfd_perror("bfd_openr error. Cannot get required BFD info");
		return -1;
	}
	r = bfd_check_format(bfd, bfd_object);
	if (!r) {
		bfd_perror("bfd_get_arch error. Cannot get required BFD info");
		return -1;
	}
	_bfd_target_name =  bfd->xvec->name;
	_bfd_arch = bfd_get_arch(bfd);
	_bfd_mach = bfd_get_mach(bfd);

	return 0;
}
/**
 * Define the version of the opagent library.
 */
#define OP_MAJOR_VERSION 1
#define OP_MINOR_VERSION 0

#define AGENT_DIR OP_SESSION_DIR_DEFAULT "jitdump"

#define MSG_MAXLEN 20

op_agent_t op_open_agent(void)
{
	char pad_bytes[7] = {0, 0, 0, 0, 0, 0, 0};
	int pad_cnt;
	char dump_path[PATH_MAX];
	char err_msg[PATH_MAX + 16];
	struct stat dirstat;
	int rc;
	struct jitheader header;
	int fd;
	struct timeval tv;
	FILE * dumpfile = NULL;

	rc = stat(AGENT_DIR, &dirstat);
	if (rc || !S_ISDIR(dirstat.st_mode)) {
		if (!rc)
			errno = ENOTDIR;
		fprintf(stderr,"libopagent: Jitdump agent directory %s "
			"missing\n", AGENT_DIR);
		fprintf(stderr,"libopagent: do opcontrol --setup or "
			"opcontrol --reset, first\n");
		return NULL;
	}
	snprintf(dump_path, PATH_MAX, "%s/%i.dump", AGENT_DIR, getpid());
	snprintf(err_msg, PATH_MAX + 16, "Error opening %s\n", dump_path);
	// make the dump file only accessible for the user for security reason.
	fd = creat(dump_path, S_IRUSR|S_IWUSR);
	if (fd == -1) {
		fprintf(stderr, "%s\n", err_msg);
		return NULL;
	}
	dumpfile = fdopen(fd, "w");
	if (!dumpfile) {
		fprintf(stderr, "%s\n", err_msg);
		return NULL;
	}
	if (define_bfd_vars())
		return NULL;
	header.magic = JITHEADER_MAGIC;
	header.version = JITHEADER_VERSION;
	header.totalsize = sizeof(header) + strlen(_bfd_target_name) + 1;
	/* calculate amount of padding '\0' */
	pad_cnt = PADDING_8ALIGNED(header.totalsize);
	header.totalsize += pad_cnt;
	header.bfd_arch = _bfd_arch;
	header.bfd_mach = _bfd_mach;
	if (gettimeofday(&tv, NULL)) {
		fprintf(stderr, "gettimeofday failed\n");
		return NULL;
	}

	header.timestamp = tv.tv_sec;
	snprintf(err_msg, PATH_MAX + 16, "Error writing to %s", dump_path);
	if (!fwrite(&header, sizeof(header), 1, dumpfile)) {
		fprintf(stderr, "%s\n", err_msg);
		return NULL;
	}
	if (!fwrite(_bfd_target_name, strlen(_bfd_target_name) + 1, 1,
		    dumpfile)) {
		fprintf(stderr, "%s\n", err_msg);
		return NULL;
	}
	/* write padding '\0' if necessary */
	if (pad_cnt && !fwrite(pad_bytes, pad_cnt, 1, dumpfile)) {
		fprintf(stderr, "%s\n", err_msg);
		return NULL;
	}
	fflush(dumpfile);
	return (op_agent_t)dumpfile;
}


int op_close_agent(op_agent_t hdl)
{
	struct jr_code_close rec;
	struct timeval tv;
	FILE * dumpfile = (FILE *) hdl;
	if (!dumpfile) {
		errno = EINVAL;
		return -1;
	}
	rec.id = JIT_CODE_CLOSE;
	rec.total_size = sizeof(rec);
	if (gettimeofday(&tv, NULL)) {
		fprintf(stderr, "gettimeofday failed\n");
		return -1;
	}
	rec.timestamp = tv.tv_sec;

	if (!fwrite(&rec, sizeof(rec), 1, dumpfile))
		return -1;
	fclose(dumpfile);
	dumpfile = NULL;
	return 0;
}


int op_write_native_code(op_agent_t hdl, char const * symbol_name,
	uint64_t vma, void const * code, unsigned int const size)
{
	struct jr_code_load rec;
	struct timeval tv;
	size_t sz_symb_name;
	char pad_bytes[7] = { 0, 0, 0, 0, 0, 0, 0 };
	size_t padding_count;
	FILE * dumpfile = (FILE *) hdl;

	if (!dumpfile) {
		errno = EINVAL;
		fprintf(stderr, "Invalid hdl argument\n");
		return -1;
	}
	sz_symb_name = strlen(symbol_name) + 1;

	rec.id = JIT_CODE_LOAD;
	rec.code_size = size;
	rec.vma = vma;
	rec.code_addr = (u64) (uintptr_t) code;
	rec.total_size = code ? sizeof(rec) + sz_symb_name + size :
			sizeof(rec) + sz_symb_name;
	/* calculate amount of padding '\0' */
	padding_count = PADDING_8ALIGNED(rec.total_size);
	rec.total_size += padding_count;
	if (gettimeofday(&tv, NULL)) {
		fprintf(stderr, "gettimeofday failed\n");
		return -1;
	}

	rec.timestamp = tv.tv_sec;

	/* locking makes sure that we continuously write this record, if
	 * we are called within a multi-threaded context */
	flockfile(dumpfile);
	/* Write record, symbol name, code (optionally), and (if necessary)
	 * additonal padding \0 bytes.
	 */
	if (fwrite_unlocked(&rec, sizeof(rec), 1, dumpfile) &&
	    fwrite_unlocked(symbol_name, sz_symb_name, 1, dumpfile)) {
		if (code)
			fwrite_unlocked(code, size, 1, dumpfile);
		if (padding_count)
			fwrite_unlocked(pad_bytes, padding_count, 1, dumpfile);
		/* Always flush to ensure conversion code to elf will see
		 * data as soon as possible */
		fflush_unlocked(dumpfile);
		funlockfile(dumpfile);
		return 0;
	}
	fflush_unlocked(dumpfile);
	funlockfile(dumpfile);
	return -1;
}


int op_write_debug_line_info(op_agent_t hdl, void const * code,
			     size_t nr_entry,
			     struct debug_line_info const * compile_map)
{
	struct jr_code_debug_info rec;
	long cur_pos, last_pos;
	struct timeval tv;
	size_t i;
	size_t padding_count;
	char padd_bytes[7] = {0, 0, 0, 0, 0, 0, 0};
	int rc = -1;
	FILE * dumpfile = (FILE *) hdl;

	if (!dumpfile) {
		errno = EINVAL;
		fprintf(stderr, "Invalid hdl argument\n");
		return -1;
	}
	
	/* write nothing if no entries are provided */
	if (nr_entry == 0)
		return 0;

	rec.id = JIT_CODE_DEBUG_INFO;
	rec.code_addr = (uint64_t)(uintptr_t)code;
	/* will be fixed after writing debug line info */
	rec.total_size = 0;
	rec.nr_entry = nr_entry;
	if (gettimeofday(&tv, NULL)) {
		fprintf(stderr, "gettimeofday failed\n");
		return -1;
	}

	rec.timestamp = tv.tv_sec;

	flockfile(dumpfile);

	if ((cur_pos = ftell(dumpfile)) == -1l)
		goto error;
	if (!fwrite_unlocked(&rec, sizeof(rec), 1, dumpfile))
		goto error;
	for (i = 0; i < nr_entry; ++i) {
		if (!fwrite_unlocked(&compile_map[i].vma,
				     sizeof(compile_map[i].vma), 1,
				     dumpfile) ||
		    !fwrite_unlocked(&compile_map[i].lineno,
				     sizeof(compile_map[i].lineno), 1,
				     dumpfile) ||
		    !fwrite_unlocked(compile_map[i].filename,
				     strlen(compile_map[i].filename) + 1, 1,
				     dumpfile))
			goto error;
	}

	if ((last_pos = ftell(dumpfile)) == -1l)
		goto error;
	rec.total_size = last_pos - cur_pos;
	padding_count = PADDING_8ALIGNED(rec.total_size);
	rec.total_size += padding_count;
	if (padding_count && !fwrite(padd_bytes, padding_count, 1, dumpfile))
		goto error;
	if (fseek(dumpfile, cur_pos, SEEK_SET) == -1l)
		goto error;
	if (!fwrite_unlocked(&rec, sizeof(rec), 1, dumpfile))
		goto error;
	if (fseek(dumpfile, last_pos + padding_count, SEEK_SET) == -1)
		goto error;

	rc = 0;
error:
	fflush_unlocked(dumpfile);
	funlockfile(dumpfile);
	return rc;
}


int op_unload_native_code(op_agent_t hdl, uint64_t vma)
{
	struct jr_code_unload rec;
	struct timeval tv;
	FILE * dumpfile = (FILE *) hdl;

	if (!dumpfile) {
		errno = EINVAL;
		fprintf(stderr, "Invalid hdl argument\n");
		return -1;
	}

	rec.id = JIT_CODE_UNLOAD;
	rec.vma = vma;
	rec.total_size = sizeof(rec);
	if (gettimeofday(&tv, NULL)) {
		fprintf(stderr, "gettimeofday failed\n");
		return -1;
	}
	rec.timestamp = tv.tv_sec;

	if (!fwrite(&rec, sizeof(rec), 1, dumpfile))
		return -1;
	fflush(dumpfile);
	return 0;
}

int op_major_version(void)
{
	return OP_MAJOR_VERSION;
}

int op_minor_version(void)
{
	return OP_MINOR_VERSION;
}