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