/*
* 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
* 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 2008 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.
*/
/*
* Increment the version whenever there is a change in the interface between
* Java and native code, whether from Java into native code:
* - LocalConsumer.h (generated by javah)
* or from native code back into Java:
* - dtj_table_load() in dtj_jnitab.c
* Changes to dtj_load_common() in dtj_util.c should not normally require a
* version update, since dtj_util.c defines classes in the JDK, not classes in
* the Java DTrace API.
*
* This version needs to match the version in LocalConsumer.java
*/
/*
* key: caller's consumer handle (int)
* value: per-consumer data includes dtrace handle (consumer_t *)
*/
static const char *dtj_getexecname(void);
boolean_t *);
dtrace_probe_f *);
dtrace_probe_f *);
void *);
void *);
static dtj_consumer_t *dtj_remove_consumer_at(int);
/*
* Gets a sequence-generated consumer ID, or NO_HANDLE if exception pending
*/
static int
{
int handle;
if (!g_dtj_load) {
return (NO_HANDLE);
}
return (NO_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
{
return (DTJ_ERR); /* java exception pending */
}
(void) pthread_mutex_lock(&g_table_lock);
if (g_consumer_table) {
} else {
}
} else {
}
(void) pthread_mutex_unlock(&g_table_lock);
return (DTJ_ERR);
}
/* Initialize java consumer */
/* Attach per-consumer data */
/* Attach per-JNI-invocation data */
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
{
int start;
if (!g_init) {
(void) pthread_mutexattr_init(&g_table_lock_attr);
(void) pthread_mutex_init(&g_table_lock,
}
(void) pthread_mutex_lock(&g_table_lock);
if (g_consumer_table == NULL) {
sizeof (dtj_consumer_t *));
if (!g_consumer_table) {
"could not allocate consumer table");
(void) pthread_mutex_unlock(&g_table_lock);
return (B_FALSE);
}
sizeof (dtj_consumer_t *)));
} else if ((g_max_consumers > 0) && (g_consumer_count >=
g_max_consumers)) {
(void) pthread_mutex_unlock(&g_table_lock);
return (B_FALSE);
} else if (g_consumer_count >= g_consumer_capacity) {
dtj_consumer_t **t;
if (g_consumer_capacity <= g_max_capacity_increment) {
} else {
}
}
t = realloc(g_consumer_table,
new_capacity * sizeof (dtj_consumer_t *));
if (!t) {
"could not reallocate consumer table");
(void) pthread_mutex_unlock(&g_table_lock);
return (B_FALSE);
}
g_consumer_table = t;
g_consumer_capacity) * sizeof (dtj_consumer_t *)));
}
/* Look for an empty slot in the table */
if (g_handle_seq >= g_consumer_capacity) {
}
++g_handle_seq;
if (g_handle_seq == start) {
" but count %d < capacity %d",
(void) pthread_mutex_unlock(&g_table_lock);
return (B_FALSE);
} else if (g_handle_seq >= g_consumer_capacity) {
}
}
g_consumer_table[g_handle_seq] = c;
*seq = g_handle_seq;
(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 *
{
return (NULL); /* java exception pending */
}
return (consumer);
}
/*
* Returns NULL if there is no consumer with the given handle. Does not throw
* exceptions.
*/
static dtj_consumer_t *
{
(void) pthread_mutex_lock(&g_table_lock);
if (g_consumer_table) {
if (g_consumer_count == 0) {
g_consumer_capacity = 0;
}
}
} else {
}
} else {
}
(void) pthread_mutex_unlock(&g_table_lock);
return (consumer);
}
/*
* Gets the name of the executable in case it is an application with an embedded
* 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)
{
/*
* The first time through, read the initial aux vector that was
* passed to the process at exec(2). Only do this once.
*/
if (fd >= 0) {
break;
}
}
}
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
{
switch (p->dtjp_type) {
case DTJ_PROGRAM_STRING:
break;
case DTJ_PROGRAM_FILE:
break;
default:
p->dtjp_type);
}
return (NULL);
}
/* Does not throw exceptions */
"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
{
if (!minProbeAttributes) {
return (NULL); /* java exception pending */
}
if (!minStatementAttributes) {
return (NULL); /* java exception pending */
}
pinfo->dpi_matches);
return (programInfo);
}
/*
* Called by LocalConsumer static initializer.
*/
/* ARGSUSED */
{
if (version != DTRACE_JNI_VERSION) {
"LocalConsumer version %d incompatible with native "
}
}
/*
* Called by LocalConsumer static initializer.
*/
/* ARGSUSED */
{
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.
*/
return;
}
/* If this fails, a Java Error (e.g. NoSuchMethodError) is pending */
g_dtj_load = B_TRUE;
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
dtj_consumer_t *c;
const char *f; /* flag name */
int oflags = 0;
int i, len;
int id;
int err;
if (!g_dtj_load) {
return;
}
c = dtj_consumer_create(env);
if (!c) {
return; /* java exception pending */
}
/* Get open flags */
for (i = 0; i < len; ++i) {
return;
}
return;
}
return;
}
if (strcmp(f, "ILP32") == 0) {
oflags |= DTRACE_O_ILP32;
} else if (strcmp(f, "LP64") == 0) {
oflags |= DTRACE_O_LP64;
}
}
/* Check for mutually exclusive flags */
"Cannot set both ILP32 and LP64");
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.)
*/
return; /* java exception pending */
}
(void) dtj_remove_consumer_at(id);
return;
}
(void) dtj_remove_consumer_at(id);
return;
}
}
static void
{
if (get) {
} else {
if (*set) {
} else {
}
}
}
/*
* Returns B_TRUE if opt is a recognized compile flag, B_FALSE otherwise.
*/
static boolean_t
{
/* see lib/libdtrace/common/dt_options.c */
} else {
}
"cannot set compile time option \"%s\" after calling go()",
opt);
return (is_cflag);
}
return (is_cflag);
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
const char *prog;
dtj_program_t *p;
int argc = 0;
return (NULL); /* java exception pending */
}
return (NULL);
}
if (!p) {
return (NULL); /* java exception pending */
}
if (args) {
dtj_program_destroy(p, NULL);
return (NULL);
}
}
"invalid probe specifier %s: %s",
dtj_program_destroy(p, NULL);
return (NULL);
}
return (jprogram); /* NULL if exception pending */
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
const char *file;
dtj_program_t *p;
int argc = 0;
return (NULL); /* java exception pending */
}
return (NULL);
}
if (!p) {
return (NULL); /* java exception pending */
}
if (args) {
dtj_program_destroy(p, NULL);
return (NULL);
}
}
"failed to compile script %s: %s", file,
dtj_program_destroy(p, NULL);
return (NULL);
}
return (jprogram); /* NULL if exception pending */
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
dtj_program_t *p;
int i;
return; /* java exception pending */
}
if (program) {
}
return;
}
/* enable all probes or those of given program only */
continue;
}
if (p->dtjp_enabled) {
return;
}
"failed to enable %s: %s", p->dtjp_name,
return;
}
p->dtjp_enabled = B_TRUE;
}
if (program) {
if (!pinfo) {
/*
* Consumer.enable() has already checked that the
* program was compiled by this consumer. This is an
* implementation error, not a user error.
*/
return;
}
if (!programInfo) {
return; /* java exception pending */
}
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
int progid;
dtj_program_t *p;
int i;
return; /* java exception pending */
}
return;
}
if (progid != i) {
/* get info of given program only */
continue;
}
}
if (!programInfo) {
return; /* java exception pending */
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
const char *opt;
const char *val;
return; /* java exception pending */
}
if (!opt) {
"could not allocate option string");
return;
}
if (value) {
if (!val) {
"could not allocate option value string");
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.
*/
}
/*
* Check for boolean compile-time options not handled by
* dtrace_setopt().
*/
if (value) {
}
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 the consumer is already running, defer setting the option
* until we wake up from dtrace_sleep.
*/
(void) pthread_mutex_lock(
if (request) {
request)) {
"Failed to add setOption request");
}
} /* else java exception pending */
(void) pthread_mutex_unlock(
} else {
dtrace_errno(dtp)));
}
}
if (value) {
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
const char *opt;
return (0); /* java exception pending */
}
if (!opt) {
"could not allocate option string");
return (0);
}
/*
* Check for boolean compile-time options not handled by
* dtrace_setopt().
*/
}
"couldn't get option %s: %s", opt,
return (0);
}
return (optval);
}
/*
* Throws IllegalStateException if not all compiled programs are enabled.
*/
{
dtj_program_t *p;
return; /* java exception pending */
}
return;
}
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:
p->dtjp_type == DTJ_PROGRAM_FILE);
}
"Not all compiled probes are enabled. "
"Compiled %s %s not enabled.",
return;
}
}
}
{
dtj_program_t *p;
return (JNI_FALSE); /* java exception pending */
}
return (JNI_FALSE); /* no program compiled */
}
if (!p->dtjp_enabled) {
return (JNI_FALSE);
}
}
return (JNI_TRUE);
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
return; /* java exception pending */
}
return; /* java exception pending */
}
"could not enable tracing: %s",
return;
}
}
/*
* Protected by global lock (LocalConsumer.class). Called when aborting the
* consumer loop before it starts.
*/
{
return; /* java exception pending */
}
"couldn't stop tracing: %s",
} else {
}
}
{
return; /* java exception pending */
}
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.
*/
struct ps_prochandle *P;
/* Must not call MonitorEnter with a pending exception */
return; /* java exception pending */
}
return; /* java exception pending */
}
0);
while ((P = dtj_pointer_list_walk_next(itr)) !=
dtrace_proc_continue(dtp, P);
}
return; /* java exception pending */
}
}
/*
* Blocking call starts consumer loop.
*/
(void) dtj_consume(&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.
*/
}
/*
* Interrupts a running consumer. May be called from any thread.
*/
{
return; /* java exception pending */
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
return; /* java exception pending */
}
/*
* 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).
*/
struct ps_prochandle *P;
0);
while ((P = dtj_pointer_list_walk_next(itr)) !=
dtrace_proc_release(dtp, P);
}
}
}
/*
* Static Consumer.java method
*/
/* ARGSUSED */
{
int open;
(void) pthread_mutex_lock(&g_table_lock);
if (g_consumer_table == NULL) {
open = 0;
} else {
}
(void) pthread_mutex_unlock(&g_table_lock);
return (open);
}
/*
* Static Consumer.java method
*/
/* ARGSUSED */
{
return (DTRACE_QUANTIZE_BUCKETVAL(bucket));
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
char dummy;
char *s;
int rc;
return (NULL); /* java exception pending */
}
/* Does not throw exceptions */
/* intValue() of class Number does not throw exceptions */
/* longValue() of class Number does not throw exceptions */
} else {
return (NULL);
}
if (!s) {
"Failed to allocate kernel function name");
return (NULL);
}
free(s);
return (jfunc);
}
/*
* Protected by global lock in Consumer.java
*/
{
char dummy;
char *s;
int rc;
return (NULL); /* java exception pending */
}
/* Does not throw exceptions */
/* intValue() of class Number does not throw exceptions */
/* longValue() of class Number does not throw exceptions */
} else {
return (NULL);
}
if (!s) {
"Failed to allocate user function name");
return (NULL);
}
free(s);
return (jfunc);
}
{
return (NULL); /* java exception pending */
}
return (NULL); /* java exception pending */
}
/*
* 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.
*/
if (!aggregate) {
/* java exception pending */
}
/*
* Cleans up only references created by this JNI invocation. Leaves
* cached per-consumer state untouched.
*/
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)
*/
{
struct ps_prochandle *P;
char **argv;
int argc;
return (-1); /* java exception pending */
}
"Could not allocate process list");
return (-1);
}
}
return (-1);
}
if (!P) {
dtrace_errno(dtp)));
return (-1);
}
"Failed to add process to process list");
dtrace_proc_release(dtp, P);
return (-1);
}
}
/*
* Protected by global lock (LocalConsumer.class)
*/
{
struct ps_prochandle *P;
return; /* java exception pending */
}
"Could not allocate process list");
return;
}
}
if (!P) {
dtrace_errno(dtp)));
return;
}
"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)
*/
{
}
{
}
static void
{
const char *probestr;
int rc;
return; /* java exception pending */
}
/* clear in-out list parameter */
return;
}
if (filter) {
return;
}
if (!probestr) {
return; /* java exception pending */
}
&probe);
if (rc == -1) {
"%s is not a valid probe description: %s",
dtrace_errno(dtp)));
return;
}
}
}
/*
* 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 */
{
if (!jprobedesc) {
return (-1); /* java exception pending */
}
/* add probe to list */
return (-1);
}
return (0);
}
/*ARGSUSED*/
static int
void *arg)
{
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.
*/
/* create probe info instance */
if (!jprobeinfo) {
return (-1); /* java exception pending */
}
}
/* create listed probe instance */
if (!jprobe) {
return (-1); /* java exception pending */
}
/* add probe to list */
jprobe);
return (-1);
}
return (0);
}
/*ARGSUSED*/
static int
{
return (0);
}
"failed to match %s:%s:%s:%s: %s",
return (1);
}
return (0);
}
/*
* Protected by global lock in Consumer.java
*/
{
}
{
}
static void
{
dtj_program_t *p;
int i;
return; /* java exception pending */
}
return;
}
if (program) {
return;
}
if (progid == -1) {
return;
}
}
continue;
}
}
}
}
/*
* Static LocalConsumer.java method
* Protected by global lock (LocalConsumer.class)
*/
/* ARGSUSED */
{
/*
* Handles the case of locale-specific encoding of the user-visible
* version string containing non-ASCII characters.
*/
}
/*
* Static LocalConsumer.java method
* Protected by global lock (LocalConsumer.class)
*/
/* ARGSUSED */
{
char *s;
int len;
name = dtj_getexecname();
if (!s) {
return (NULL);
}
free(s);
return (jname);
}
/*
* Static LocalConsumer.java method
*/
/* ARGSUSED */
{
}
/*
* Static LocalConsumer.java method
*/
/* ARGSUSED */
{
}
{
dtj_consumer_t *c;
if (c == NULL) {
return; /* java exception pending */
}
}