diff options
Diffstat (limited to 'usr/src/lib/libdtrace_jni/common/dtrace_jni.c')
-rw-r--r-- | usr/src/lib/libdtrace_jni/common/dtrace_jni.c | 1893 |
1 files changed, 1893 insertions, 0 deletions
diff --git a/usr/src/lib/libdtrace_jni/common/dtrace_jni.c b/usr/src/lib/libdtrace_jni/common/dtrace_jni.c new file mode 100644 index 0000000000..089d13ac74 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtrace_jni.c @@ -0,0 +1,1893 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <libgen.h> +#include <assert.h> +#include <strings.h> +#include <libproc.h> +#include <pthread.h> +#include <dtrace_jni.h> +/* generated by javah */ +#include <LocalConsumer.h> + +/* + * dtrace_jni.c defines all the native methods of the Java DTrace API. Every + * native method is declared in a single class, LocalConsumer.java. + * + * Notes: + * + * The data generating loop must explicitly release every object reference it + * obtains in order to avoid a memory leak. A local JNI object reference is not + * automatically released until control returns to java, which never happens as + * long as the data generating loop runs. This applies to any JNI function that + * obtains an object reference (such as CallObjectMethod() or NewObject()). A + * local reference is released by calling DeleteLocalRef(), which is safe to + * call with an exception pending. + * + * It is important to check for an exception after calling java code from native + * C, such as after notifying the java consumer of new data. Failure to do this + * makes it possible for users of the interface to crash the JVM by throwing an + * exception in java code. + * + * Some JNI functions, like GetIntField() or ReleaseStringUTFChars(), do not + * need to be checked for exceptions. + * + * GetStringUTFChars() returns NULL if and only if an exception was thrown. + * + * It is important to stop a DTrace consumer and remove it if an exception + * occurs. This API guarantees that a consumer is stopped automatically if it + * throws an exception. An application running multiple DTrace consumers + * simultaneously should be able to continue running the others normally if any + * fail. + * + * Calls to libdtrace are not guaranteed to be MT-safe. Even if they are + * currently MT-safe, they are not guaranteed to remain that way. To address + * this, a global lock (the LocalConsumer.class reference) is used around calls + * to libdtrace. In many cases, the locking is done in java, which should be + * indicated in this file by a comment above the function that assumes prior + * locking. To access the same global lock from native C code, the JNI function + * MonitorEnter() is used. Each MonitorEnter() must have a matching + * MonitorExit() or the application will hang (all consumer threads). The + * consumer loop and the getAggregate() method require a per-consumer lock + * rather than a global lock; in that case the argument to MonitorEnter() and + * MonitorExit() is the consumerLock member of the LocalConsumer, not the + * LocalConsumer itself. + */ + +#define DTRACE_JNI_VERSION 1 + +#define FIRST_HANDLE 0 /* sequence-generated consumer ID */ +#define NO_HANDLE -1 +#define INITIAL_CAPACITY 8 /* initial size of consumer array */ +#define MAX_CAPACITY_INCREMENT 1024 + +static boolean_t g_dtj_load = B_FALSE; +static int g_handle_seq = NO_HANDLE; +/* + * key: caller's consumer handle (int) + * value: per-consumer data includes dtrace handle (consumer_t *) + */ +static dtj_consumer_t **g_consumer_table = NULL; +static size_t g_consumer_capacity = 0; +static size_t g_consumer_count = 0; +static size_t g_max_capacity_increment = MAX_CAPACITY_INCREMENT; +static size_t g_max_consumers = 0; /* no maximum */ +static boolean_t g_init = B_FALSE; +static pthread_mutex_t g_table_lock; +static pthread_mutexattr_t g_table_lock_attr; +pthread_key_t g_dtj_consumer_key; + +static int dtj_get_handle(JNIEnv *, jobject); +static dtj_status_t dtj_get_java_consumer(JNIEnv *, jobject, + dtj_java_consumer_t *); +static const char *dtj_getexecname(void); +static jobject dtj_get_program_info(dtj_java_consumer_t *, dtrace_proginfo_t *); +static jobject dtj_add_program(dtj_java_consumer_t *, dtj_program_t *); +static void dtj_flag(uint_t *, uint_t, boolean_t *, boolean_t *); +static boolean_t dtj_cflag(dtj_java_consumer_t *, const char *, boolean_t *, + boolean_t *); +static void dtj_list_probes(JNIEnv *, jobject, jobject, jobject, + dtrace_probe_f *); +static void dtj_list_compiled_probes(JNIEnv *, jobject, jobject, jobject, + dtrace_probe_f *); +static int dtj_list_probe(dtrace_hdl_t *, const dtrace_probedesc_t *, void *); +static int dtj_list_probe_detail(dtrace_hdl_t *, const dtrace_probedesc_t *, + void *); +static int dtj_list_stmt(dtrace_hdl_t *, dtrace_prog_t *, dtrace_stmtdesc_t *, + void *); +static boolean_t dtj_add_consumer(JNIEnv *, dtj_consumer_t *, int *); +static dtj_consumer_t *dtj_remove_consumer(JNIEnv *, jobject); +static dtj_consumer_t *dtj_remove_consumer_at(int); + +/* + * Gets a sequence-generated consumer ID, or NO_HANDLE if exception pending + */ +static int +dtj_get_handle(JNIEnv *jenv, jobject caller) +{ + int handle; + + if (!g_dtj_load) { + dtj_throw_illegal_state(jenv, "JNI table not loaded"); + return (NO_HANDLE); + } + handle = (*jenv)->CallIntMethod(jenv, caller, g_gethandle_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NO_HANDLE); + } + if (handle == NO_HANDLE) { + dtj_throw_illegal_state(jenv, "no consumer handle"); + } + return (handle); +} + +/* + * Populates the given java consumer created for use in the current native + * method call. If the return value is DTJ_ERR, a java exception is pending. + * Throws IllegalStateException if the caller does not have a valid handle. + * Throws NoSuchElementException if the caller's handle is not in the global + * caller table. + */ +static dtj_status_t +dtj_get_java_consumer(JNIEnv *jenv, jobject caller, dtj_java_consumer_t *jc) +{ + dtj_consumer_t *consumer; + int handle = dtj_get_handle(jenv, caller); + if (handle == NO_HANDLE) { + return (DTJ_ERR); /* java exception pending */ + } + (void) pthread_mutex_lock(&g_table_lock); + if (g_consumer_table) { + if ((handle >= 0) && (handle < g_consumer_capacity)) { + consumer = g_consumer_table[handle]; + } else { + consumer = NULL; + } + } else { + consumer = NULL; + } + (void) pthread_mutex_unlock(&g_table_lock); + if (consumer == NULL) { + dtj_throw_no_such_element(jenv, "consumer handle %d", handle); + return (DTJ_ERR); + } + + /* Initialize java consumer */ + bzero(jc, sizeof (dtj_java_consumer_t)); + + /* Attach per-consumer data */ + jc->dtjj_consumer = consumer; + + /* Attach per-JNI-invocation data */ + jc->dtjj_caller = caller; + jc->dtjj_jenv = jenv; + + return (DTJ_OK); +} + +/* + * Adds a consumer to the global consumer table. + * Returns B_TRUE if successful; a java exception is pending otherwise. + * Postcondition: if successful, g_handle_seq is the handle of the consumer just + * added. + */ +static boolean_t +dtj_add_consumer(JNIEnv *jenv, dtj_consumer_t *c, int *seq) +{ + int start; + + if (!g_init) { + (void) pthread_key_create(&g_dtj_consumer_key, NULL); + (void) pthread_mutexattr_init(&g_table_lock_attr); + (void) pthread_mutexattr_settype(&g_table_lock_attr, + PTHREAD_MUTEX_RECURSIVE); + (void) pthread_mutex_init(&g_table_lock, + &g_table_lock_attr); + g_init = B_TRUE; + } + + *seq = NO_HANDLE; + (void) pthread_mutex_lock(&g_table_lock); + if (g_consumer_table == NULL) { + g_consumer_table = malloc(INITIAL_CAPACITY * + sizeof (dtj_consumer_t *)); + if (!g_consumer_table) { + g_handle_seq = NO_HANDLE; + dtj_throw_out_of_memory(jenv, + "could not allocate consumer table"); + (void) pthread_mutex_unlock(&g_table_lock); + return (B_FALSE); + } + bzero(g_consumer_table, (INITIAL_CAPACITY * + sizeof (dtj_consumer_t *))); + g_consumer_capacity = INITIAL_CAPACITY; + } else if (g_consumer_count >= g_consumer_capacity) { + dtj_consumer_t **t; + size_t new_capacity; + + if ((g_max_consumers > 0) && (g_consumer_count >= + g_max_consumers)) { + dtj_throw_resource_limit(jenv, "Too many consumers"); + (void) pthread_mutex_unlock(&g_table_lock); + return (B_FALSE); + } + + if (g_consumer_capacity <= g_max_capacity_increment) { + new_capacity = (g_consumer_capacity * 2); + } else { + new_capacity = (g_consumer_capacity + + g_max_capacity_increment); + } + + if ((g_max_consumers > 0) && (new_capacity > g_max_consumers)) { + new_capacity = g_max_consumers; + } + + t = realloc(g_consumer_table, + new_capacity * sizeof (dtj_consumer_t *)); + if (!t) { + dtj_throw_out_of_memory(jenv, + "could not reallocate consumer table"); + (void) pthread_mutex_unlock(&g_table_lock); + return (B_FALSE); + } + + g_consumer_table = t; + bzero(g_consumer_table + g_consumer_capacity, ((new_capacity - + g_consumer_capacity) * sizeof (dtj_consumer_t *))); + g_consumer_capacity = new_capacity; + } + + /* Look for an empty slot in the table */ + g_handle_seq = (g_handle_seq == NO_HANDLE + ? FIRST_HANDLE : g_handle_seq + 1); + if (g_handle_seq >= g_consumer_capacity) { + g_handle_seq = FIRST_HANDLE; + } + start = g_handle_seq; /* guard against infinite loop */ + while (g_consumer_table[g_handle_seq] != NULL) { + ++g_handle_seq; + if (g_handle_seq == start) { + dtj_throw_illegal_state(jenv, "consumer table full," + " but count %d < capacity %d", + g_consumer_count, g_consumer_capacity); + (void) pthread_mutex_unlock(&g_table_lock); + return (B_FALSE); + } else if (g_handle_seq >= g_consumer_capacity) { + g_handle_seq = FIRST_HANDLE; + } + } + g_consumer_table[g_handle_seq] = c; + *seq = g_handle_seq; + ++g_consumer_count; + (void) pthread_mutex_unlock(&g_table_lock); + return (B_TRUE); +} + +/* + * Removes a consumer from the global consumer table. The call may be initiated + * from Java code or from native code (because an exception has occurred). + * Precondition: no exception pending (any pending exception must be temporarily + * cleared) + * Returns NULL if the caller is not in the table or if this function throws an + * exception; either case leaves the global consumer table unchanged. + * Throws IllegalStateException if the caller does not have a valid handle. + */ +static dtj_consumer_t * +dtj_remove_consumer(JNIEnv *jenv, jobject caller) +{ + dtj_consumer_t *consumer; + int handle = dtj_get_handle(jenv, caller); + if (handle == NO_HANDLE) { + return (NULL); /* java exception pending */ + } + consumer = dtj_remove_consumer_at(handle); + return (consumer); +} + +/* + * Returns NULL if there is no consumer with the given handle. Does not throw + * exceptions. + */ +static dtj_consumer_t * +dtj_remove_consumer_at(int handle) +{ + dtj_consumer_t *consumer; + (void) pthread_mutex_lock(&g_table_lock); + if (g_consumer_table) { + if ((handle >= 0) && (handle < g_consumer_capacity)) { + consumer = g_consumer_table[handle]; + if (consumer != NULL) { + g_consumer_table[handle] = NULL; + --g_consumer_count; + if (g_consumer_count == 0) { + free(g_consumer_table); + g_consumer_table = NULL; + g_consumer_capacity = 0; + g_handle_seq = NO_HANDLE; + } + } + } else { + consumer = NULL; + } + } else { + consumer = NULL; + } + (void) pthread_mutex_unlock(&g_table_lock); + return (consumer); +} + +/* + * Gets the name of the executable in case it is an application with an embedded + * JVM and not "java". Implementation is copied from lib/mpss/common/mpss.c. + * The use of static auxv_t makes the MT-level unsafe. The caller is expected + * to use the global lock (LocalConsumer.class). + */ +static const char * +dtj_getexecname(void) +{ + const char *execname = NULL; + static auxv_t auxb; + + /* + * The first time through, read the initial aux vector that was + * passed to the process at exec(2). Only do this once. + */ + int fd = open("/proc/self/auxv", O_RDONLY); + + if (fd >= 0) { + while (read(fd, &auxb, sizeof (auxv_t)) == sizeof (auxv_t)) { + if (auxb.a_type == AT_SUN_EXECNAME) { + execname = auxb.a_un.a_ptr; + break; + } + } + (void) close(fd); + } + return (execname); +} + +/* + * Add the compiled program to a list of programs the API expects to enable. + * Returns the Program instance identifying the listed program, or NULL if the + * Program constructor fails (exception pending in that case). + */ +static jobject +dtj_add_program(dtj_java_consumer_t *jc, dtj_program_t *p) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jobject jprogram = NULL; + + switch (p->dtjp_type) { + case DTJ_PROGRAM_STRING: + jprogram = (*jenv)->NewObject(jenv, g_program_jc, + g_proginit_jm); + break; + case DTJ_PROGRAM_FILE: + jprogram = (*jenv)->NewObject(jenv, g_programfile_jc, + g_fproginit_jm); + break; + default: + dtj_throw_illegal_argument(jenv, "unexpected program type %d\n", + p->dtjp_type); + } + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + + /* Does not throw exceptions */ + (*jenv)->SetIntField(jenv, jprogram, g_progid_jf, + uu_list_numnodes(jc->dtjj_consumer->dtjc_program_list)); + + if (!dtj_list_add(jc->dtjj_consumer->dtjc_program_list, p)) { + (*jenv)->DeleteLocalRef(jenv, jprogram); + dtj_throw_out_of_memory(jenv, + "could not add program"); + return (NULL); + } + + return (jprogram); +} + +/* + * Returns a new ProgramInfo, or NULL if the constructor fails (java exception + * pending in that case). + */ +static jobject +dtj_get_program_info(dtj_java_consumer_t *jc, dtrace_proginfo_t *pinfo) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jobject minProbeAttributes = NULL; + jobject minStatementAttributes = NULL; + jobject programInfo = NULL; /* return value */ + + minProbeAttributes = dtj_new_attribute(jc, &pinfo->dpi_descattr); + if (!minProbeAttributes) { + return (NULL); /* java exception pending */ + } + minStatementAttributes = dtj_new_attribute(jc, &pinfo->dpi_stmtattr); + if (!minStatementAttributes) { + (*jenv)->DeleteLocalRef(jenv, minProbeAttributes); + return (NULL); /* java exception pending */ + } + + programInfo = (*jenv)->NewObject(jenv, g_proginfo_jc, + g_proginfoinit_jm, minProbeAttributes, minStatementAttributes, + pinfo->dpi_matches); + (*jenv)->DeleteLocalRef(jenv, minProbeAttributes); + (*jenv)->DeleteLocalRef(jenv, minStatementAttributes); + + return (programInfo); +} + +/* + * Called by LocalConsumer static initializer. + */ +JNIEXPORT void JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1checkVersion(JNIEnv *env, + jclass class, jint version) +{ + if (version != DTRACE_JNI_VERSION) { + dtj_throw_illegal_state(env, + "LocalConsumer version %d incompatible with native " + "implementation version %d", version, DTRACE_JNI_VERSION); + } +} + +/* + * Called by LocalConsumer static initializer. + */ +JNIEXPORT void JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1loadJniTable(JNIEnv *env, + jclass class) +{ + if (g_dtj_load) { + /* + * JNI table includes a global reference to the LocalConsumer + * class, preventing the class from being unloaded. The + * LocalConsumer static initializer should never execute more + * than once. + */ + dtj_throw_illegal_state(env, "JNI table already loaded"); + return; + } + + /* If this fails, a Java Error (e.g. NoSuchMethodError) is pending */ + if (dtj_load(env) == DTJ_OK) { + g_dtj_load = B_TRUE; + } +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1open(JNIEnv *env, jobject obj, + jobjectArray flags) +{ + dtrace_hdl_t *dtp; + dtj_consumer_t *c; + const char *f; /* flag name */ + int oflags = 0; + int i, len; + int id; + int err; + + jobject flag = NULL; + jstring flagname = NULL; + + if (!g_dtj_load) { + dtj_throw_illegal_state(env, "JNI table not loaded"); + return; + } + + c = dtj_consumer_create(env); + if (!c) { + return; /* java exception pending */ + } + + /* Get open flags */ + len = (flags ? (*env)->GetArrayLength(env, flags) : 0); + for (i = 0; i < len; ++i) { + flag = (*env)->GetObjectArrayElement(env, flags, i); + if ((*env)->ExceptionCheck(env)) { + dtj_consumer_destroy(c); + return; + } + + flagname = (*env)->CallObjectMethod(env, flag, g_enumname_jm); + (*env)->DeleteLocalRef(env, flag); + if ((*env)->ExceptionCheck(env)) { + dtj_consumer_destroy(c); + return; + } + f = (*env)->GetStringUTFChars(env, flagname, NULL); + if ((*env)->ExceptionCheck(env)) { + (*env)->DeleteLocalRef(env, flagname); + dtj_consumer_destroy(c); + return; + } + if (strcmp(f, "ILP32") == 0) { + oflags |= DTRACE_O_ILP32; + } else if (strcmp(f, "LP64") == 0) { + oflags |= DTRACE_O_LP64; + } + (*env)->ReleaseStringUTFChars(env, flagname, f); + (*env)->DeleteLocalRef(env, flagname); + } + + /* Check for mutually exclusive flags */ + if ((oflags & DTRACE_O_ILP32) && (oflags & DTRACE_O_LP64)) { + dtj_throw_illegal_argument(env, + "Cannot set both ILP32 and LP64"); + dtj_consumer_destroy(c); + return; + } + + /* + * Make sure we can add the consumer before calling dtrace_open(). + * Repeated calls to open() when the consumer table is maxed out should + * avoid calling dtrace_open(). (Normally there is no limit to the size + * of the consumer table, but the undocumented JAVA_DTRACE_MAX_CONSUMERS + * system property lets you set a limit after which + * ResourceLimitException is thrown.) + */ + if (!dtj_add_consumer(env, c, &id)) { + dtj_consumer_destroy(c); + return; /* java exception pending */ + } + + (*env)->CallVoidMethod(env, obj, g_sethandle_jm, id); + if ((*env)->ExceptionCheck(env)) { + (void) dtj_remove_consumer_at(id); + dtj_consumer_destroy(c); + return; + } + + if ((dtp = dtrace_open(DTRACE_VERSION, oflags, &err)) == NULL) { + dtj_java_consumer_t jc; + jc.dtjj_jenv = env; + dtj_throw_dtrace_exception(&jc, dtrace_errmsg(NULL, err)); + (void) dtj_remove_consumer_at(id); + dtj_consumer_destroy(c); + return; + } + c->dtjc_dtp = dtp; /* set consumer handle to native DTrace library */ +} + +static void +dtj_flag(uint_t *flags, uint_t flag, boolean_t *get, boolean_t *set) +{ + assert((get && !set) || (set && !get)); + + if (get) { + *get = (*flags & flag); + } else { + if (*set) { + *flags |= flag; + } else { + *flags &= ~flag; + } + } +} + +/* + * Returns B_TRUE if opt is a recognized compile flag, B_FALSE otherwise. + */ +static boolean_t +dtj_cflag(dtj_java_consumer_t *jc, const char *opt, boolean_t *get, + boolean_t *set) +{ + boolean_t is_cflag = B_TRUE; + uint_t *flags = &jc->dtjj_consumer->dtjc_cflags; + + /* see lib/libdtrace/common/dt_option.c */ + if (strcmp(opt, "argref") == 0) { + dtj_flag(flags, DTRACE_C_ARGREF, get, set); + } else if (strcmp(opt, "cpp") == 0) { + dtj_flag(flags, DTRACE_C_CPP, get, set); + } else if (strcmp(opt, "defaultargs") == 0) { + dtj_flag(flags, DTRACE_C_DEFARG, get, set); + } else if (strcmp(opt, "empty") == 0) { + dtj_flag(flags, DTRACE_C_EMPTY, get, set); + } else if (strcmp(opt, "errtags") == 0) { + dtj_flag(flags, DTRACE_C_ETAGS, get, set); + } else if (strcmp(opt, "knodefs") == 0) { + dtj_flag(flags, DTRACE_C_KNODEF, get, set); + } else if (strcmp(opt, "nolibs") == 0) { + dtj_flag(flags, DTRACE_C_NOLIBS, get, set); + } else if (strcmp(opt, "pspec") == 0) { + dtj_flag(flags, DTRACE_C_PSPEC, get, set); + } else if (strcmp(opt, "unodefs") == 0) { + dtj_flag(flags, DTRACE_C_UNODEF, get, set); + } else if (strcmp(opt, "verbose") == 0) { + dtj_flag(flags, DTRACE_C_DIFV, get, set); + } else if (strcmp(opt, "zdefs") == 0) { + dtj_flag(flags, DTRACE_C_ZDEFS, get, set); + } else { + is_cflag = B_FALSE; + } + + if (is_cflag && set && + (jc->dtjj_consumer->dtjc_state != DTJ_CONSUMER_INIT)) { + dtj_throw_illegal_state(jc->dtjj_jenv, + "cannot set compile time option \"%s\" after calling go()", + opt); + return (is_cflag); + } + + return (is_cflag); +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jobject JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1compileString(JNIEnv *env, + jobject obj, jstring program, jobjectArray args) +{ + const char *prog; + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + dtj_program_t *p; + int argc = 0; + char **argv = NULL; + + jstring jprogram = NULL; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + prog = (*env)->GetStringUTFChars(env, program, 0); + if ((*env)->ExceptionCheck(env)) { + return (NULL); + } + + p = dtj_program_create(env, DTJ_PROGRAM_STRING, prog); + if (!p) { + (*env)->ReleaseStringUTFChars(env, program, prog); + return (NULL); /* java exception pending */ + } + + if (args) { + argv = dtj_get_argv(env, args, &argc); + if ((*env)->ExceptionCheck(env)) { + (*env)->ReleaseStringUTFChars(env, program, prog); + dtj_program_destroy(p, NULL); + return (NULL); + } + } + + if ((p->dtjp_program = dtrace_program_strcompile(dtp, + prog, DTRACE_PROBESPEC_NAME, jc.dtjj_consumer->dtjc_cflags, + argc, argv)) == NULL) { + dtj_throw_dtrace_exception(&jc, + "invalid probe specifier %s: %s", + prog, dtrace_errmsg(dtp, dtrace_errno(dtp))); + (*env)->ReleaseStringUTFChars(env, program, prog); + dtj_program_destroy(p, NULL); + dtj_free_argv(argv); + return (NULL); + } + (*env)->ReleaseStringUTFChars(env, program, prog); + dtj_free_argv(argv); + + jprogram = dtj_add_program(&jc, p); + return (jprogram); /* NULL if exception pending */ +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jobject JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1compileFile(JNIEnv *env, + jobject obj, jstring filename, jobjectArray args) +{ + FILE *fp; + const char *file; + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + dtj_program_t *p; + int argc = 0; + char **argv = NULL; + + jstring jprogram = NULL; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + file = dtj_GetStringNativeChars(env, filename); + if ((fp = fopen(file, "r")) == NULL) { + dtj_throw_dtrace_exception(&jc, "failed to open %s", file); + dtj_ReleaseStringNativeChars(env, filename, file); + return (NULL); + } + + p = dtj_program_create(env, DTJ_PROGRAM_FILE, file); + if (!p) { + (void) fclose(fp); + dtj_ReleaseStringNativeChars(env, filename, file); + return (NULL); /* java exception pending */ + } + + if (args) { + argv = dtj_get_argv(env, args, &argc); + if ((*env)->ExceptionCheck(env)) { + (void) fclose(fp); + dtj_ReleaseStringNativeChars(env, filename, file); + dtj_program_destroy(p, NULL); + return (NULL); + } + } + + if ((p->dtjp_program = dtrace_program_fcompile(dtp, + fp, jc.dtjj_consumer->dtjc_cflags, argc, argv)) == NULL) { + dtj_throw_dtrace_exception(&jc, + "failed to compile script %s: %s", file, + dtrace_errmsg(dtp, dtrace_errno(dtp))); + (void) fclose(fp); + dtj_ReleaseStringNativeChars(env, filename, file); + dtj_program_destroy(p, NULL); + dtj_free_argv(argv); + return (NULL); + } + (void) fclose(fp); + dtj_ReleaseStringNativeChars(env, filename, file); + dtj_free_argv(argv); + + jprogram = dtj_add_program(&jc, p); + return (jprogram); /* NULL if exception pending */ +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1exec(JNIEnv *env, jobject obj, + jobject program) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + int progid = -1; + uu_list_walk_t *itr; + dtj_program_t *p; + dtrace_proginfo_t *pinfo = NULL; + int i; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + if (program) { + progid = (*env)->GetIntField(env, program, g_progid_jf); + } + + if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) { + dtj_throw_illegal_state(env, "no program compiled"); + return; + } + + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0); + for (i = 0; (p = uu_list_walk_next(itr)) != NULL; ++i) { + /* enable all probes or those of given program only */ + if ((progid != -1) && (progid != i)) { + continue; + } + + if (p->dtjp_enabled) { + dtj_throw_illegal_state(env, "program already enabled"); + uu_list_walk_end(itr); + return; + } + + pinfo = &p->dtjp_info; + if (dtrace_program_exec(dtp, p->dtjp_program, pinfo) == -1) { + dtj_throw_dtrace_exception(&jc, + "failed to enable %s: %s", p->dtjp_name, + dtrace_errmsg(dtp, dtrace_errno(dtp))); + uu_list_walk_end(itr); + return; + } + p->dtjp_enabled = B_TRUE; + } + uu_list_walk_end(itr); + + if (program) { + jobject programInfo = NULL; + + if (!pinfo) { + /* + * Consumer.enable() has already checked that the + * program was compiled by this consumer. This is an + * implementation error, not a user error. + */ + dtj_throw_illegal_state(env, "program not found"); + return; + } + + programInfo = dtj_get_program_info(&jc, pinfo); + if (!programInfo) { + return; /* java exception pending */ + } + + (*env)->SetObjectField(env, program, g_proginfo_jf, + programInfo); + (*env)->DeleteLocalRef(env, programInfo); + } +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1getProgramInfo(JNIEnv *env, + jobject obj, jobject program) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + int progid; + uu_list_walk_t *itr; + dtj_program_t *p; + dtrace_proginfo_t *pinfo = NULL; + int i; + + jobject programInfo = NULL; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + progid = (*env)->GetIntField(env, program, g_progid_jf); + + if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) { + dtj_throw_illegal_state(env, "no program compiled"); + return; + } + + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0); + for (i = 0; ((p = uu_list_walk_next(itr)) != NULL) && !pinfo; ++i) { + if (progid != i) { + /* get info of given program only */ + continue; + } + + pinfo = &p->dtjp_info; + dtrace_program_info(dtp, p->dtjp_program, pinfo); + } + uu_list_walk_end(itr); + + programInfo = dtj_get_program_info(&jc, pinfo); + if (!programInfo) { + return; /* java exception pending */ + } + + (*env)->SetObjectField(env, program, g_proginfo_jf, + programInfo); + (*env)->DeleteLocalRef(env, programInfo); +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1setOption(JNIEnv *env, + jobject obj, jstring option, jstring value) +{ + dtj_java_consumer_t jc; + const char *opt; + const char *val; + boolean_t on; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + + opt = (*env)->GetStringUTFChars(env, option, 0); + if (!opt) { + dtj_throw_out_of_memory(env, + "could not allocate option string"); + return; + } + + if (value) { + val = (*env)->GetStringUTFChars(env, value, 0); + if (!val) { + dtj_throw_out_of_memory(env, + "could not allocate option value string"); + (*env)->ReleaseStringUTFChars(env, option, opt); + return; + } + } else { + /* + * dtrace_setopt() sets option to 0 if value is NULL. That's + * not the same thing as unsetting a boolean option, since + * libdtrace uses -2 to mean unset. We'll leave it to + * LocalConsumer.java to disallow null or not. + */ + val = NULL; + } + + /* + * Check for boolean compile-time options not handled by + * dtrace_setopt(). + */ + on = (!val || (strcmp(val, "unset") != 0)); + if (dtj_cflag(&jc, opt, NULL, &on)) { + (*env)->ReleaseStringUTFChars(env, option, opt); + if (value) { + (*env)->ReleaseStringUTFChars(env, value, val); + } + return; + } + + /* + * The transition from INIT to GO is protected by synchronization + * (a per-consumer lock) in LocalConsumer.java, ensuring that go() and + * setOption() execute sequentially. + */ + if (jc.dtjj_consumer->dtjc_state != DTJ_CONSUMER_INIT) { + /* + * If the consumer is already running, defer setting the option + * until we wake up from dtrace_sleep. + */ + dtj_request_t *request; + + + (void) pthread_mutex_lock( + &jc.dtjj_consumer->dtjc_request_list_lock); + request = dtj_request_create(env, DTJ_REQUEST_OPTION, opt, val); + if (request) { + if (!dtj_list_add(jc.dtjj_consumer->dtjc_request_list, + request)) { + dtj_throw_out_of_memory(env, + "Failed to add setOption request"); + } + } /* else java exception pending */ + (void) pthread_mutex_unlock( + &jc.dtjj_consumer->dtjc_request_list_lock); + } else { + dtrace_hdl_t *dtp = jc.dtjj_consumer->dtjc_dtp; + if (dtrace_setopt(dtp, opt, val) == -1) { + dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp, + dtrace_errno(dtp))); + } + } + + (*env)->ReleaseStringUTFChars(env, option, opt); + if (value) { + (*env)->ReleaseStringUTFChars(env, value, val); + } +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jlong JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1getOption(JNIEnv *env, + jobject obj, jstring option) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + const char *opt; + dtrace_optval_t optval; + boolean_t cflag; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return (0); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + opt = (*env)->GetStringUTFChars(env, option, 0); + if (!opt) { + dtj_throw_out_of_memory(env, + "could not allocate option string"); + return (0); + } + + /* + * Check for boolean compile-time options not handled by + * dtrace_setopt(). + */ + if (dtj_cflag(&jc, opt, &cflag, NULL)) { + (*env)->ReleaseStringUTFChars(env, option, opt); + return (cflag ? 1 : DTRACEOPT_UNSET); + } + + if (dtrace_getopt(dtp, opt, &optval) == -1) { + dtj_throw_dtrace_exception(&jc, + "couldn't get option %s: %s", opt, + dtrace_errmsg(dtp, dtrace_errno(dtp))); + (*env)->ReleaseStringUTFChars(env, option, opt); + return (0); + } + (*env)->ReleaseStringUTFChars(env, option, opt); + return (optval); +} + +/* + * Throws IllegalStateException if not all compiled programs are enabled. + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1checkProgramEnabling(JNIEnv *env, + jobject obj) +{ + dtj_java_consumer_t jc; + dtj_program_t *p; + uu_list_walk_t *itr; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + + if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) { + dtj_throw_illegal_state(env, "no program compiled"); + return; + } + + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0); + while ((p = uu_list_walk_next(itr)) != NULL) { + if (!p->dtjp_enabled) { + const char *type; + switch (p->dtjp_type) { + case DTJ_PROGRAM_STRING: + type = "description"; + break; + case DTJ_PROGRAM_FILE: + type = "script"; + break; + default: + assert(p->dtjp_type == DTJ_PROGRAM_STRING || + p->dtjp_type == DTJ_PROGRAM_FILE); + } + dtj_throw_illegal_state(env, + "Not all compiled probes are enabled. " + "Compiled %s %s not enabled.", + type, p->dtjp_name); + uu_list_walk_end(itr); + return; + } + } + uu_list_walk_end(itr); +} + +JNIEXPORT jboolean JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1isEnabled(JNIEnv *env, + jobject obj) +{ + dtj_java_consumer_t jc; + dtj_program_t *p; + uu_list_walk_t *itr; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return (JNI_FALSE); /* java exception pending */ + } + + if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) { + return (JNI_FALSE); /* no program compiled */ + } + + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0); + while ((p = uu_list_walk_next(itr)) != NULL) { + if (!p->dtjp_enabled) { + uu_list_walk_end(itr); + return (JNI_FALSE); + } + } + uu_list_walk_end(itr); + + return (JNI_TRUE); +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1go(JNIEnv *env, jobject obj) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + if (dtj_set_callback_handlers(&jc) != DTJ_OK) { + return; /* java exception pending */ + } + + if (dtrace_go(dtp) != 0) { + dtj_throw_dtrace_exception(&jc, + "could not enable tracing: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return; + } + + jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_GO; +} + +/* + * Protected by global lock (LocalConsumer.class). Called when aborting the + * consumer loop before it starts. + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1stop(JNIEnv *env, + jobject obj) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + if (dtrace_stop(dtp) == -1) { + dtj_throw_dtrace_exception(&jc, + "couldn't stop tracing: %s", + dtrace_errmsg(jc.dtjj_consumer->dtjc_dtp, + dtrace_errno(jc.dtjj_consumer->dtjc_dtp))); + } else { + jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_STOP; + } +} + +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1consume(JNIEnv *env, + jobject obj) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + jc.dtjj_consumer->dtjc_state = DTJ_CONSUMER_START; + + if (dtj_java_consumer_init(env, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + + /* + * Must set the thread-specific java consumer before starting the + * dtrace_work() loop because the bufhandler can also be invoked by + * getAggregate() from another thread. The bufhandler needs access to + * the correct JNI state specific to either the consumer loop or the + * getAggregate() call. + */ + (void) pthread_setspecific(g_dtj_consumer_key, &jc); + + if (jc.dtjj_consumer->dtjc_process_list != NULL) { + struct ps_prochandle *P; + uu_list_walk_t *itr; + + (*env)->MonitorEnter(env, g_caller_jc); + if ((*env)->ExceptionCheck(env)) { + dtj_java_consumer_fini(env, &jc); + return; /* java exception pending */ + } + + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_process_list, + 0); + while ((P = dtj_pointer_list_walk_next(itr)) != + DTJ_INVALID_PTR) { + dtrace_proc_continue(dtp, P); + } + uu_list_walk_end(itr); + + (*env)->MonitorExit(env, g_caller_jc); + if ((*env)->ExceptionCheck(env)) { + dtj_java_consumer_fini(env, &jc); + return; /* java exception pending */ + } + } + + /* + * Blocking call starts consumer loop. + */ + (void) dtj_consume(&jc); + + dtj_java_consumer_fini(env, &jc); + /* + * Stop the consumer after the consumer loop terminates, whether + * normally because of the exit() action or LocalConsumer.stop(), or + * abnormally because of an exception. The call is ignored if the + * consumer is already stopped. It is safe to call dtj_stop() with a + * pending exception. + */ + dtj_stop(&jc); +} + +/* + * Interrupts a running consumer. May be called from any thread. + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1interrupt(JNIEnv *env, + jobject obj) +{ + dtj_java_consumer_t jc; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + + jc.dtjj_consumer->dtjc_interrupt = B_TRUE; +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1close(JNIEnv *env, jobject obj) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + /* + * Need to release any created procs here, since the consumer_t + * destructor (called by _destroy) will not have access to the dtrace + * handle needed to release them (this function closes the dtrace + * handle). + */ + if (jc.dtjj_consumer->dtjc_process_list != NULL) { + struct ps_prochandle *P; + uu_list_walk_t *itr; + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_process_list, + 0); + while ((P = dtj_pointer_list_walk_next(itr)) != + DTJ_INVALID_PTR) { + dtrace_proc_release(dtp, P); + } + uu_list_walk_end(itr); + } + + dtrace_close(dtp); +} + +/* + * Static Consumer.java method + */ +JNIEXPORT jint JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1openCount(JNIEnv *env, jclass c) +{ + int open; + (void) pthread_mutex_lock(&g_table_lock); + if (g_consumer_table == NULL) { + open = 0; + } else { + open = g_consumer_count; + } + (void) pthread_mutex_unlock(&g_table_lock); + return (open); +} + +/* + * Static Consumer.java method + */ +JNIEXPORT jlong JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1quantizeBucket(JNIEnv *env, + jclass c, jint bucket) +{ + return (DTRACE_QUANTIZE_BUCKETVAL(bucket)); +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jstring JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1lookupKernelFunction( + JNIEnv *jenv, jobject caller, jobject address) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + jstring jfunc; /* return value */ + + GElf_Addr addr; + char dummy; + char *s; + int rc; + + if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + /* Does not throw exceptions */ + if ((*jenv)->IsInstanceOf(jenv, address, g_int_jc)) { + /* intValue() of class Number does not throw exceptions */ + addr = (GElf_Addr)(uint32_t)(*jenv)->CallIntMethod(jenv, + address, g_intval_jm); + } else if ((*jenv)->IsInstanceOf(jenv, address, g_number_jc)) { + /* longValue() of class Number does not throw exceptions */ + addr = (GElf_Addr)(*jenv)->CallLongMethod(jenv, + address, g_longval_jm); + } else { + dtj_throw_class_cast(jenv, "Expected Number address"); + return (NULL); + } + + rc = dtrace_addr2str(dtp, addr, &dummy, 1); + s = malloc(rc); + if (!s) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate kernel function name"); + return (NULL); + } + (void) dtrace_addr2str(dtp, addr, s, rc); + + jfunc = (*jenv)->NewStringUTF(jenv, s); + free(s); + return (jfunc); +} + +/* + * Protected by global lock in Consumer.java + */ +JNIEXPORT jstring JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1lookupUserFunction(JNIEnv *jenv, + jobject caller, jint pid, jobject address) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + + jstring jfunc; /* return value */ + + GElf_Addr addr; + char dummy; + char *s; + int rc; + + if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + /* Does not throw exceptions */ + if ((*jenv)->IsInstanceOf(jenv, address, g_int_jc)) { + /* intValue() of class Number does not throw exceptions */ + addr = (GElf_Addr)(uint32_t)(*jenv)->CallIntMethod(jenv, + address, g_intval_jm); + } else if ((*jenv)->IsInstanceOf(jenv, address, g_number_jc)) { + /* longValue() of class Number does not throw exceptions */ + addr = (GElf_Addr)(*jenv)->CallLongMethod(jenv, + address, g_longval_jm); + } else { + dtj_throw_class_cast(jenv, "Expected Number address"); + return (NULL); + } + + rc = dtrace_uaddr2str(dtp, pid, addr, &dummy, 1); + s = malloc(rc); + if (!s) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate user function name"); + return (NULL); + } + (void) dtrace_uaddr2str(dtp, pid, addr, s, rc); + + jfunc = (*jenv)->NewStringUTF(jenv, s); + free(s); + return (jfunc); +} + +JNIEXPORT jobject JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1getAggregate(JNIEnv *env, + jobject obj, jobject spec) +{ + dtj_java_consumer_t jc; + + jobject aggregate = NULL; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + + if (dtj_java_consumer_init(env, &jc) != DTJ_OK) { + return (NULL); /* java exception pending */ + } + jc.dtjj_aggregate_spec = spec; + + /* + * Must set the thread-specific java consumer before calling any + * function that triggers callbacks to the bufhandler set by + * dtrace_handle_buffered(). The bufhandler needs access to the correct + * JNI state specific to either the consumer loop or the + * getAggregate() call. + */ + (void) pthread_setspecific(g_dtj_consumer_key, &jc); + aggregate = dtj_get_aggregate(&jc); + if (!aggregate) { + /* java exception pending */ + jc.dtjj_consumer->dtjc_interrupt = B_TRUE; + } + /* + * Cleans up only references created by this JNI invocation. Leaves + * cached per-consumer state untouched. + */ + dtj_java_consumer_fini(env, &jc); + return (aggregate); +} + +/* + * Returns the pid of the created process, or -1 in case of an error (java + * exception pending). + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT int JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1createProcess(JNIEnv *jenv, + jobject caller, jstring command) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + struct ps_prochandle *P; + char **argv; + int argc; + + if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) { + return (-1); /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + if (jc.dtjj_consumer->dtjc_process_list == NULL) { + jc.dtjj_consumer->dtjc_process_list = dtj_pointer_list_create(); + if (!jc.dtjj_consumer->dtjc_process_list) { + dtj_throw_out_of_memory(jenv, + "Could not allocate process list"); + return (-1); + } + } + + argv = dtj_make_argv(jenv, command, &argc); + if ((*jenv)->ExceptionCheck(jenv)) { + return (-1); + } + + P = dtrace_proc_create(dtp, argv[0], argv); + dtj_free_argv(argv); + + if (!P) { + dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp, + dtrace_errno(dtp))); + return (-1); + } + + if (!dtj_pointer_list_add(jc.dtjj_consumer->dtjc_process_list, P)) { + dtj_throw_out_of_memory(jenv, + "Failed to add process to process list"); + dtrace_proc_release(dtp, P); + return (-1); + } + return (Pstatus(P)->pr_pid); +} + +/* + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1grabProcess(JNIEnv *jenv, + jobject caller, jint pid) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + struct ps_prochandle *P; + + if (dtj_get_java_consumer(jenv, caller, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + if (jc.dtjj_consumer->dtjc_process_list == NULL) { + jc.dtjj_consumer->dtjc_process_list = dtj_pointer_list_create(); + if (jc.dtjj_consumer->dtjc_process_list == NULL) { + dtj_throw_out_of_memory(jenv, + "Could not allocate process list"); + return; + } + } + + P = dtrace_proc_grab(dtp, (pid_t)pid, 0); + if (!P) { + dtj_throw_dtrace_exception(&jc, dtrace_errmsg(dtp, + dtrace_errno(dtp))); + return; + } + + if (!dtj_pointer_list_add(jc.dtjj_consumer->dtjc_process_list, P)) { + dtj_throw_out_of_memory(jenv, + "Failed to add process to process list"); + dtrace_proc_release(dtp, P); + return; + } +} + +/* + * Lists all probes, or lists matching probes (using the matching rules from + * Table 4-1 of the DTrace manual). + * + * In the future it may be desirable to support an array of probe filters rather + * than a single filter. It could be that if a probe matched any of the given + * filters, it would be included (implied logical OR). + * + * Protected by global lock (LocalConsumer.class) + * param list: an empty list to populate (this function empties the list if it + * is not empty already) + * param filter: a ProbeDescription instance; the list will include only probes + * that match the filter (match all probes if filter is null) + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1listProbes(JNIEnv *env, + jobject obj, jobject list, jobject filter) +{ + dtj_list_probes(env, obj, list, filter, dtj_list_probe); +} + +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1listProbeDetail(JNIEnv *env, + jobject obj, jobject list, jobject filter) +{ + dtj_list_probes(env, obj, list, filter, dtj_list_probe_detail); +} + +static void +dtj_list_probes(JNIEnv *env, jobject obj, jobject list, jobject filter, + dtrace_probe_f *func) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + dtrace_probedesc_t probe; + dtrace_probedesc_t *pdp = NULL; + const char *probestr; + int rc; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + + jc.dtjj_probelist = list; + + /* clear in-out list parameter */ + (*env)->CallVoidMethod(env, list, g_listclear_jm); + if ((*env)->ExceptionCheck(env)) { + return; + } + + if (filter) { + jstring jprobestr = NULL; + + jprobestr = (*env)->CallObjectMethod(env, filter, + g_tostring_jm); + if ((*env)->ExceptionCheck(env)) { + return; + } + probestr = (*env)->GetStringUTFChars(env, jprobestr, NULL); + if (!probestr) { + (*env)->DeleteLocalRef(env, jprobestr); + return; /* java exception pending */ + } + + bzero(&probe, sizeof (probe)); + rc = dtrace_str2desc(dtp, DTRACE_PROBESPEC_NAME, probestr, + &probe); + (*env)->ReleaseStringUTFChars(env, jprobestr, probestr); + (*env)->DeleteLocalRef(env, jprobestr); + if (rc == -1) { + dtj_throw_dtrace_exception(&jc, + "%s is not a valid probe description: %s", + probestr, dtrace_errmsg(dtp, + dtrace_errno(dtp))); + return; + } + + pdp = &probe; + } + + (void) dtrace_probe_iter(dtp, pdp, func, &jc); +} + +/* + * Returns 0 to indicate success, or -1 to cause dtrace_probe_iter() to return a + * negative value prematurely (indicating no match or failure). + */ +static int +/* ARGSUSED */ +dtj_list_probe(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp, void *arg) +{ + dtj_java_consumer_t *jc = arg; + JNIEnv *jenv = jc->dtjj_jenv; + + jobject jprobedesc = NULL; + + jprobedesc = dtj_new_probedesc(jc, pdp); + if (!jprobedesc) { + return (-1); /* java exception pending */ + } + + /* add probe to list */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probelist, g_listadd_jm, + jprobedesc); + (*jenv)->DeleteLocalRef(jenv, jprobedesc); + if ((*jenv)->ExceptionCheck(jenv)) { + return (-1); + } + + return (0); +} + +/*ARGSUSED*/ +static int +dtj_list_probe_detail(dtrace_hdl_t *dtp, const dtrace_probedesc_t *pdp, + void *arg) +{ + dtj_java_consumer_t *jc = arg; + JNIEnv *jenv = jc->dtjj_jenv; + dtrace_probeinfo_t p; + + jobject jprobedesc = NULL; + jobject jprobeinfo = NULL; + jobject jprobe = NULL; + + jprobedesc = dtj_new_probedesc(jc, pdp); + if (!jprobedesc) { + return (-1); /* java exception pending */ + } + + /* + * If dtrace_probe_info() returns a non-zero value, dtrace_errno is set + * for us. In that case, ignore the dtrace error and simply omit probe + * info. That error is implicitly cleared the next time a call is made + * using the same dtrace handle. + */ + if (dtrace_probe_info(dtp, pdp, &p) == 0) { + /* create probe info instance */ + jprobeinfo = dtj_new_probeinfo(jc, &p); + if (!jprobeinfo) { + (*jenv)->DeleteLocalRef(jenv, jprobedesc); + return (-1); /* java exception pending */ + } + } + + /* create listed probe instance */ + jprobe = (*jenv)->NewObject(jenv, g_probe_jc, g_probeinit_jm, + jprobedesc, jprobeinfo); + (*jenv)->DeleteLocalRef(jenv, jprobedesc); + (*jenv)->DeleteLocalRef(jenv, jprobeinfo); + if (!jprobe) { + return (-1); /* java exception pending */ + } + + /* add probe to list */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probelist, g_listadd_jm, + jprobe); + (*jenv)->DeleteLocalRef(jenv, jprobe); + if ((*jenv)->ExceptionCheck(jenv)) { + return (-1); + } + + return (0); +} + +/*ARGSUSED*/ +static int +dtj_list_stmt(dtrace_hdl_t *dtp, dtrace_prog_t *pgp, + dtrace_stmtdesc_t *stp, void *arg) +{ + dtj_java_consumer_t *jc = arg; + dtrace_ecbdesc_t *edp = stp->dtsd_ecbdesc; + + if (edp == jc->dtjj_consumer->dtjc_last_probe) { + return (0); + } + + if (dtrace_probe_iter(dtp, &edp->dted_probe, + jc->dtjj_consumer->dtjc_plistfunc, arg) != 0) { + dtj_throw_dtrace_exception(jc, + "failed to match %s:%s:%s:%s: %s", + edp->dted_probe.dtpd_provider, edp->dted_probe.dtpd_mod, + edp->dted_probe.dtpd_func, edp->dted_probe.dtpd_name, + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (1); + } + + jc->dtjj_consumer->dtjc_last_probe = edp; + return (0); +} + +/* + * Protected by global lock in Consumer.java + */ +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1listCompiledProbes(JNIEnv *env, + jobject obj, jobject list, jobject program) +{ + dtj_list_compiled_probes(env, obj, list, program, dtj_list_probe); +} + +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1listCompiledProbeDetail( + JNIEnv *env, jobject obj, jobject list, jobject program) +{ + dtj_list_compiled_probes(env, obj, list, program, + dtj_list_probe_detail); +} + +static void +dtj_list_compiled_probes(JNIEnv *env, jobject obj, jobject list, + jobject program, dtrace_probe_f *func) +{ + dtj_java_consumer_t jc; + dtrace_hdl_t *dtp; + uu_list_walk_t *itr; + dtj_program_t *p; + boolean_t found; + int progid = -1; + int i; + + if (dtj_get_java_consumer(env, obj, &jc) != DTJ_OK) { + return; /* java exception pending */ + } + dtp = jc.dtjj_consumer->dtjc_dtp; + jc.dtjj_probelist = list; + + (*env)->CallVoidMethod(env, list, g_listclear_jm); + if ((*env)->ExceptionCheck(env)) { + return; + } + + if (program) { + if (dtj_list_empty(jc.dtjj_consumer->dtjc_program_list)) { + dtj_throw_no_such_element(env, "no compiled program"); + return; + } + progid = (*env)->GetIntField(env, program, g_progid_jf); + if (progid == -1) { + dtj_throw_illegal_argument(env, "invalid program"); + return; + } + } + + jc.dtjj_consumer->dtjc_plistfunc = func; + found = B_FALSE; + itr = uu_list_walk_start(jc.dtjj_consumer->dtjc_program_list, 0); + for (i = 0; (p = uu_list_walk_next(itr)) != NULL; ++i) { + if ((progid != -1) && (progid != i)) { + continue; + } + + found = B_TRUE; + (void) dtrace_stmt_iter(dtp, p->dtjp_program, + (dtrace_stmt_f *)dtj_list_stmt, &jc); + } + uu_list_walk_end(itr); + + if (program && !found) { + dtj_throw_no_such_element(env, "program not found"); + } +} + +/* + * Static LocalConsumer.java method + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jstring JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1getVersion(JNIEnv *env, + jclass class) +{ + /* + * Handles the case of locale-specific encoding of the user-visible + * version string containing non-ASCII characters. + */ + return (dtj_NewStringNative(env, _dtrace_version)); +} + +/* + * Static LocalConsumer.java method + * Protected by global lock (LocalConsumer.class) + */ +JNIEXPORT jstring JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1getExecutableName(JNIEnv *env, + jclass class) +{ + jstring jname = NULL; + const char *name = NULL; + char *s; + int len; + + name = dtj_getexecname(); + len = strlen(name); + s = malloc(len + 1); + if (!s) { + dtj_throw_out_of_memory(env, "Failed to allocate execname"); + return (NULL); + } + (void) strcpy(s, name); + name = basename(s); + free(s); + jname = (*env)->NewStringUTF(env, name); + return (jname); +} + +/* + * Static LocalConsumer.java method + */ +JNIEXPORT void JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1setMaximumConsumers(JNIEnv *env, + jclass class, jint max) +{ + g_max_consumers = max; +} + +/* + * Static LocalConsumer.java method + */ +JNIEXPORT void JNICALL +/* ARGSUSED */ +Java_org_opensolaris_os_dtrace_LocalConsumer__1setDebug(JNIEnv *env, + jclass class, jboolean debug) +{ + g_dtj_util_debug = debug; +} + +JNIEXPORT void JNICALL +Java_org_opensolaris_os_dtrace_LocalConsumer__1destroy(JNIEnv *env, jobject obj) +{ + dtj_consumer_t *c; + + c = dtj_remove_consumer(env, obj); + if (c == NULL) { + return; /* java exception pending */ + } + dtj_consumer_destroy(c); +} |