Mercurial > hg > truffle
diff src/os/solaris/dtrace/jvm_dtrace.c @ 0:a61af66fc99e jdk7-b24
Initial load
author | duke |
---|---|
date | Sat, 01 Dec 2007 00:00:00 +0000 |
parents | |
children | c18cbe5936b8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/os/solaris/dtrace/jvm_dtrace.c Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,565 @@ +/* + * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + * + */ + +#include <door.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <thread.h> +#include <unistd.h> +#include "jvm_dtrace.h" + +// NOTE: These constants are used in JVM code as well. +// KEEP JVM CODE IN SYNC if you are going to change these... + +#define DTRACE_ALLOC_PROBES 0x1 +#define DTRACE_METHOD_PROBES 0x2 +#define DTRACE_MONITOR_PROBES 0x4 +#define DTRACE_ALL_PROBES -1 + +// generic error messages +#define JVM_ERR_OUT_OF_MEMORY "out of memory (native heap)" +#define JVM_ERR_INVALID_PARAM "invalid input parameter(s)" +#define JVM_ERR_NULL_PARAM "input paramater is NULL" + +// error messages for attach +#define JVM_ERR_CANT_OPEN_DOOR "cannot open door file" +#define JVM_ERR_CANT_CREATE_ATTACH_FILE "cannot create attach file" +#define JVM_ERR_DOOR_FILE_PERMISSION "door file is not secure" +#define JVM_ERR_CANT_SIGNAL "cannot send SIGQUIT to target" + +// error messages for enable probe +#define JVM_ERR_DOOR_CMD_SEND "door command send failed" +#define JVM_ERR_DOOR_CANT_READ_STATUS "cannot read door command status" +#define JVM_ERR_DOOR_CMD_STATUS "door command error status" + +// error message for detach +#define JVM_ERR_CANT_CLOSE_DOOR "cannot close door file" + +#define RESTARTABLE(_cmd, _result) do { \ + do { \ + _result = _cmd; \ + } while((_result == -1) && (errno == EINTR)); \ +} while(0) + +struct _jvm_t { + pid_t pid; + int door_fd; +}; + +static int libjvm_dtrace_debug; +static void print_debug(const char* fmt,...) { + if (libjvm_dtrace_debug) { + va_list alist; + va_start(alist, fmt); + fputs("libjvm_dtrace DEBUG: ", stderr); + vfprintf(stderr, fmt, alist); + va_end(alist); + } +} + +/* Key for thread local error message */ +static thread_key_t jvm_error_key; + +/* init function for this library */ +static void init_jvm_dtrace() { + /* check for env. var for debug mode */ + libjvm_dtrace_debug = getenv("LIBJVM_DTRACE_DEBUG") != NULL; + /* create key for thread local error message */ + if (thr_keycreate(&jvm_error_key, NULL) != 0) { + print_debug("can't create thread_key_t for jvm error key\n"); + // exit(1); ? + } +} + +#pragma init(init_jvm_dtrace) + +/* set thread local error message */ +static void set_jvm_error(const char* msg) { + thr_setspecific(jvm_error_key, (void*)msg); +} + +/* clear thread local error message */ +static void clear_jvm_error() { + thr_setspecific(jvm_error_key, NULL); +} + +/* file handling functions that can handle interrupt */ + +static int file_open(const char* path, int flag) { + int ret; + RESTARTABLE(open(path, flag), ret); + return ret; +} + +static int file_close(int fd) { + int ret; + RESTARTABLE(close(fd), ret); + return ret; +} + +static int file_read(int fd, char* buf, int len) { + int ret; + RESTARTABLE(read(fd, buf, len), ret); + return ret; +} + +/* send SIGQUIT signal to given process */ +static int send_sigquit(pid_t pid) { + int ret; + RESTARTABLE(kill(pid, SIGQUIT), ret); + return ret; +} + +/* called to check permissions on attach file */ +static int check_permission(const char* path) { + struct stat64 sb; + uid_t uid, gid; + int res; + + /* + * Check that the path is owned by the effective uid/gid of this + * process. Also check that group/other access is not allowed. + */ + uid = geteuid(); + gid = getegid(); + + res = stat64(path, &sb); + if (res != 0) { + print_debug("stat failed for %s\n", path); + return -1; + } + + if ((sb.st_uid != uid) || (sb.st_gid != gid) || + ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0)) { + print_debug("well-known file %s is not secure\n", path); + return -1; + } + return 0; +} + +#define ATTACH_FILE_PATTERN "/tmp/.attach_pid%d" + +/* fill-in the name of attach file name in given buffer */ +static void fill_attach_file_name(char* path, int len, pid_t pid) { + memset(path, 0, len); + sprintf(path, ATTACH_FILE_PATTERN, pid); +} + +#define DOOR_FILE_PATTERN "/tmp/.java_pid%d" + +/* open door file for the given JVM */ +static int open_door(pid_t pid) { + char path[PATH_MAX + 1]; + int fd; + + sprintf(path, DOOR_FILE_PATTERN, pid); + fd = file_open(path, O_RDONLY); + if (fd < 0) { + set_jvm_error(JVM_ERR_CANT_OPEN_DOOR); + print_debug("cannot open door file %s\n", path); + return -1; + } + print_debug("opened door file %s\n", path); + if (check_permission(path) != 0) { + set_jvm_error(JVM_ERR_DOOR_FILE_PERMISSION); + print_debug("check permission failed for %s\n", path); + file_close(fd); + fd = -1; + } + return fd; +} + +/* create attach file for given process */ +static int create_attach_file(pid_t pid) { + char path[PATH_MAX + 1]; + int fd; + fill_attach_file_name(path, sizeof(path), pid); + fd = file_open(path, O_CREAT | O_RDWR); + if (fd < 0) { + set_jvm_error(JVM_ERR_CANT_CREATE_ATTACH_FILE); + print_debug("cannot create file %s\n", path); + } else { + print_debug("created attach file %s\n", path); + } + return fd; +} + +/* delete attach file for given process */ +static void delete_attach_file(pid_t pid) { + char path[PATH_MAX + 1]; + fill_attach_file_name(path, sizeof(path), pid); + int res = unlink(path); + if (res) { + print_debug("cannot delete attach file %s\n", path); + } else { + print_debug("deleted attach file %s\n", path); + } +} + +/* attach to given JVM */ +jvm_t* jvm_attach(pid_t pid) { + jvm_t* jvm; + int door_fd, attach_fd, i; + + jvm = (jvm_t*) calloc(1, sizeof(jvm_t)); + if (jvm == NULL) { + set_jvm_error(JVM_ERR_OUT_OF_MEMORY); + print_debug("calloc failed in %s at %d\n", __FILE__, __LINE__); + return NULL; + } + jvm->pid = pid; + attach_fd = -1; + + door_fd = open_door(pid); + if (door_fd < 0) { + print_debug("trying to create attach file\n"); + if ((attach_fd = create_attach_file(pid)) < 0) { + goto quit; + } + + /* send QUIT signal to the target so that it will + * check for the attach file. + */ + if (send_sigquit(pid) != 0) { + set_jvm_error(JVM_ERR_CANT_SIGNAL); + print_debug("sending SIGQUIT failed\n"); + goto quit; + } + + /* give the target VM time to start the attach mechanism */ + do { + int res; + RESTARTABLE(poll(0, 0, 200), res); + door_fd = open_door(pid); + i++; + } while (i <= 50 && door_fd == -1); + if (door_fd < 0) { + print_debug("Unable to open door to process %d\n", pid); + goto quit; + } + } + +quit: + if (attach_fd >= 0) { + file_close(attach_fd); + delete_attach_file(jvm->pid); + } + if (door_fd >= 0) { + jvm->door_fd = door_fd; + clear_jvm_error(); + } else { + free(jvm); + jvm = NULL; + } + return jvm; +} + +/* return the last thread local error message */ +const char* jvm_get_last_error() { + const char* res = NULL; + thr_getspecific(jvm_error_key, (void**)&res); + return res; +} + +/* detach the givenb JVM */ +int jvm_detach(jvm_t* jvm) { + if (jvm) { + int res; + if (jvm->door_fd != -1) { + if (file_close(jvm->door_fd) != 0) { + set_jvm_error(JVM_ERR_CANT_CLOSE_DOOR); + res = -1; + } else { + clear_jvm_error(); + res = 0; + } + } + free(jvm); + return res; + } else { + set_jvm_error(JVM_ERR_NULL_PARAM); + print_debug("jvm_t* is NULL\n"); + return -1; + } +} + +/* + * A simple table to translate some known errors into reasonable + * error messages + */ +static struct { + int err; + const char* msg; +} const error_messages[] = { + { 100, "Bad request" }, + { 101, "Protocol mismatch" }, + { 102, "Resource failure" }, + { 103, "Internal error" }, + { 104, "Permission denied" }, +}; + +/* + * Lookup the given error code and return the appropriate + * message. If not found return NULL. + */ +static const char* translate_error(int err) { + int table_size = sizeof(error_messages) / sizeof(error_messages[0]); + int i; + + for (i=0; i<table_size; i++) { + if (err == error_messages[i].err) { + return error_messages[i].msg; + } + } + return NULL; +} + +/* + * Current protocol version + */ +static const char* PROTOCOL_VERSION = "1"; + +#define RES_BUF_SIZE 128 + +/* + * Enqueue attach-on-demand command to the given JVM + */ +static +int enqueue_command(jvm_t* jvm, const char* cstr, int arg_count, const char** args) { + size_t size; + door_arg_t door_args; + char res_buffer[RES_BUF_SIZE]; + int rc, i; + char* buf = NULL; + int result = -1; + + /* + * First we get the command string and create the start of the + * argument string to send to the target VM: + * <ver>\0<cmd>\0 + */ + if (cstr == NULL) { + print_debug("command name is NULL\n"); + goto quit; + } + size = strlen(PROTOCOL_VERSION) + strlen(cstr) + 2; + buf = (char*)malloc(size); + if (buf != NULL) { + char* pos = buf; + strcpy(buf, PROTOCOL_VERSION); + pos += strlen(PROTOCOL_VERSION)+1; + strcpy(pos, cstr); + } else { + set_jvm_error(JVM_ERR_OUT_OF_MEMORY); + print_debug("malloc failed at %d in %s\n", __LINE__, __FILE__); + goto quit; + } + + /* + * Next we iterate over the arguments and extend the buffer + * to include them. + */ + for (i=0; i<arg_count; i++) { + cstr = args[i]; + if (cstr != NULL) { + size_t len = strlen(cstr); + char* newbuf = (char*)realloc(buf, size+len+1); + if (newbuf == NULL) { + set_jvm_error(JVM_ERR_OUT_OF_MEMORY); + print_debug("realloc failed in %s at %d\n", __FILE__, __LINE__); + goto quit; + } + buf = newbuf; + strcpy(buf+size, cstr); + size += len+1; + } + } + + /* + * The arguments to the door function are in 'buf' so we now + * do the door call + */ + door_args.data_ptr = buf; + door_args.data_size = size; + door_args.desc_ptr = NULL; + door_args.desc_num = 0; + door_args.rbuf = (char*)&res_buffer; + door_args.rsize = sizeof(res_buffer); + + RESTARTABLE(door_call(jvm->door_fd, &door_args), rc); + + /* + * door_call failed + */ + if (rc == -1) { + print_debug("door_call failed\n"); + } else { + /* + * door_call succeeded but the call didn't return the the expected jint. + */ + if (door_args.data_size < sizeof(int)) { + print_debug("Enqueue error - reason unknown as result is truncated!"); + } else { + int* res = (int*)(door_args.data_ptr); + if (*res != 0) { + const char* msg = translate_error(*res); + if (msg == NULL) { + print_debug("Unable to enqueue command to target VM: %d\n", *res); + } else { + print_debug("Unable to enqueue command to target VM: %s\n", msg); + } + } else { + /* + * The door call should return a file descriptor to one end of + * a socket pair + */ + if ((door_args.desc_ptr != NULL) && + (door_args.desc_num == 1) && + (door_args.desc_ptr->d_attributes & DOOR_DESCRIPTOR)) { + result = door_args.desc_ptr->d_data.d_desc.d_descriptor; + } else { + print_debug("Reply from enqueue missing descriptor!\n"); + } + } + } + } + +quit: + if (buf) free(buf); + return result; +} + +/* read status code for a door command */ +static int read_status(int fd) { + char ch, buf[16]; + int index = 0; + + while (1) { + if (file_read(fd, &ch, sizeof(ch)) != sizeof(ch)) { + set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS); + print_debug("door cmd status: read status failed\n"); + return -1; + } + buf[index++] = ch; + if (ch == '\n') { + buf[index - 1] = '\0'; + return atoi(buf); + } + if (index == sizeof(buf)) { + set_jvm_error(JVM_ERR_DOOR_CANT_READ_STATUS); + print_debug("door cmd status: read status overflow\n"); + return -1; + } + } +} + +static const char* ENABLE_DPROBES_CMD = "enabledprobes"; + +/* enable one or more DTrace probes for a given JVM */ +int jvm_enable_dtprobes(jvm_t* jvm, int num_probe_types, const char** probe_types) { + int fd, status = 0; + char ch; + const char* args[1]; + char buf[16]; + int probe_type = 0, index; + int count = 0; + + if (jvm == NULL) { + set_jvm_error(JVM_ERR_NULL_PARAM); + print_debug("jvm_t* is NULL\n"); + return -1; + } + + if (num_probe_types == 0 || probe_types == NULL || + probe_types[0] == NULL) { + set_jvm_error(JVM_ERR_INVALID_PARAM); + print_debug("invalid probe type argument(s)\n"); + return -1; + } + + for (index = 0; index < num_probe_types; index++) { + const char* p = probe_types[index]; + if (strcmp(p, JVM_DTPROBE_OBJECT_ALLOC) == 0) { + probe_type |= DTRACE_ALLOC_PROBES; + count++; + } else if (strcmp(p, JVM_DTPROBE_METHOD_ENTRY) == 0 || + strcmp(p, JVM_DTPROBE_METHOD_RETURN) == 0) { + probe_type |= DTRACE_METHOD_PROBES; + count++; + } else if (strcmp(p, JVM_DTPROBE_MONITOR_ENTER) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_ENTERED) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_EXIT) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_WAIT) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_WAITED) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_NOTIFY) == 0 || + strcmp(p, JVM_DTPROBE_MONITOR_NOTIFYALL) == 0) { + probe_type |= DTRACE_MONITOR_PROBES; + count++; + } else if (strcmp(p, JVM_DTPROBE_ALL) == 0) { + probe_type |= DTRACE_ALL_PROBES; + count++; + } + } + + if (count == 0) { + return count; + } + sprintf(buf, "%d", probe_type); + args[0] = buf; + + fd = enqueue_command(jvm, ENABLE_DPROBES_CMD, 1, args); + if (fd < 0) { + set_jvm_error(JVM_ERR_DOOR_CMD_SEND); + return -1; + } + + status = read_status(fd); + // non-zero status is error + if (status) { + set_jvm_error(JVM_ERR_DOOR_CMD_STATUS); + print_debug("%s command failed (status: %d) in target JVM\n", + ENABLE_DPROBES_CMD, status); + file_close(fd); + return -1; + } + // read from stream until EOF + while (file_read(fd, &ch, sizeof(ch)) == sizeof(ch)) { + if (libjvm_dtrace_debug) { + printf("%c", ch); + } + } + + file_close(fd); + clear_jvm_error(); + return count; +}