alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (jvm_dtrace.c)

This example Java source code file (jvm_dtrace.c) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

attach_file_pattern, door_file_pattern, dtrace_all_probes, dtrace_alloc_probes, dtrace_method_probes, dtrace_monitor_probes, enable_dprobes_cmd, null, null\\n, protocol_version, restartable, sigquit, unable

The jvm_dtrace.c Java example source code

/*
 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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) {
    return close(fd);
}

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\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;
}

Other Java examples (source code examples)

Here is a short list of links related to this Java jvm_dtrace.c source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.