/**
* @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;
}