From fb3fb4f3d76d55b64440afd0af72775dfad3bd1d Mon Sep 17 00:00:00 2001 From: tomee Date: Thu, 16 Feb 2006 12:15:27 -0800 Subject: PSARC 2006/054 DTrace JNI Binding 6384263 PSARC 2006/054 DTrace JNI Binding --- usr/src/lib/Makefile | 10 +- usr/src/lib/libdtrace_jni/Makefile | 67 + usr/src/lib/libdtrace_jni/Makefile.com | 94 + usr/src/lib/libdtrace_jni/amd64/Makefile | 34 + usr/src/lib/libdtrace_jni/common/dtj_attr.c | 181 ++ usr/src/lib/libdtrace_jni/common/dtj_consume.c | 2247 ++++++++++++++++++++ usr/src/lib/libdtrace_jni/common/dtj_error.c | 79 + usr/src/lib/libdtrace_jni/common/dtj_jnitab.c | 853 ++++++++ usr/src/lib/libdtrace_jni/common/dtj_probe.c | 108 + usr/src/lib/libdtrace_jni/common/dtj_util.c | 1506 +++++++++++++ usr/src/lib/libdtrace_jni/common/dtj_util.h | 376 ++++ usr/src/lib/libdtrace_jni/common/dtrace_jni.c | 1893 +++++++++++++++++ usr/src/lib/libdtrace_jni/common/dtrace_jni.h | 491 +++++ usr/src/lib/libdtrace_jni/i386/Makefile | 33 + usr/src/lib/libdtrace_jni/java/Makefile | 242 +++ .../libdtrace_jni/java/docs/examples/TestAPI.java | 62 + .../libdtrace_jni/java/docs/examples/TestAPI2.java | 80 + .../java/docs/examples/TestTarget.java | 81 + .../lib/libdtrace_jni/java/docs/examples/hello.d | 33 + .../libdtrace_jni/java/docs/examples/intrstat.d | 43 + .../lib/libdtrace_jni/java/docs/examples/syscall.d | 39 + .../lib/libdtrace_jni/java/docs/examples/target.d | 33 + .../java/docs/html/JavaDTraceAPI.html | 207 ++ usr/src/lib/libdtrace_jni/java/docs/html/fast.html | 542 +++++ .../java/docs/images/JavaDTraceAPI.gif | Bin 0 -> 27447 bytes usr/src/lib/libdtrace_jni/java/manifest/dtrace.mf | 11 + .../os/dtrace/AbstractAggregationValue.java | 108 + .../src/org/opensolaris/os/dtrace/Aggregate.java | 278 +++ .../org/opensolaris/os/dtrace/AggregateSpec.java | 163 ++ .../src/org/opensolaris/os/dtrace/Aggregation.java | 368 ++++ .../opensolaris/os/dtrace/AggregationRecord.java | 208 ++ .../opensolaris/os/dtrace/AggregationValue.java | 52 + .../src/org/opensolaris/os/dtrace/AvgValue.java | 154 ++ .../src/org/opensolaris/os/dtrace/Consumer.java | 751 +++++++ .../org/opensolaris/os/dtrace/ConsumerAdapter.java | 79 + .../org/opensolaris/os/dtrace/ConsumerEvent.java | 71 + .../opensolaris/os/dtrace/ConsumerException.java | 107 + .../opensolaris/os/dtrace/ConsumerListener.java | 127 ++ .../src/org/opensolaris/os/dtrace/CountValue.java | 106 + .../org/opensolaris/os/dtrace/DTraceException.java | 46 + .../src/org/opensolaris/os/dtrace/DataEvent.java | 117 + .../org/opensolaris/os/dtrace/Distribution.java | 620 ++++++ .../java/src/org/opensolaris/os/dtrace/Drop.java | 274 +++ .../src/org/opensolaris/os/dtrace/DropEvent.java | 114 + .../java/src/org/opensolaris/os/dtrace/Error.java | 324 +++ .../src/org/opensolaris/os/dtrace/ErrorEvent.java | 116 + .../opensolaris/os/dtrace/ExceptionHandler.java | 54 + .../src/org/opensolaris/os/dtrace/ExitRecord.java | 94 + .../java/src/org/opensolaris/os/dtrace/Flow.java | 234 ++ .../opensolaris/os/dtrace/InterfaceAttributes.java | 529 +++++ .../opensolaris/os/dtrace/KernelStackRecord.java | 382 ++++ .../opensolaris/os/dtrace/LinearDistribution.java | 291 +++ .../org/opensolaris/os/dtrace/LocalConsumer.java | 1332 ++++++++++++ .../org/opensolaris/os/dtrace/LogDistribution.java | 175 ++ .../src/org/opensolaris/os/dtrace/MaxValue.java | 81 + .../src/org/opensolaris/os/dtrace/MinValue.java | 81 + .../org/opensolaris/os/dtrace/NativeException.java | 70 + .../java/src/org/opensolaris/os/dtrace/Option.java | 681 ++++++ .../org/opensolaris/os/dtrace/PrintaRecord.java | 482 +++++ .../org/opensolaris/os/dtrace/PrintfRecord.java | 221 ++ .../java/src/org/opensolaris/os/dtrace/Probe.java | 201 ++ .../src/org/opensolaris/os/dtrace/ProbeData.java | 693 ++++++ .../opensolaris/os/dtrace/ProbeDescription.java | 467 ++++ .../src/org/opensolaris/os/dtrace/ProbeInfo.java | 207 ++ .../org/opensolaris/os/dtrace/ProcessEvent.java | 118 + .../org/opensolaris/os/dtrace/ProcessState.java | 358 ++++ .../src/org/opensolaris/os/dtrace/Program.java | 259 +++ .../src/org/opensolaris/os/dtrace/ProgramInfo.java | 252 +++ .../java/src/org/opensolaris/os/dtrace/Record.java | 36 + .../os/dtrace/ResourceLimitException.java | 50 + .../org/opensolaris/os/dtrace/ScalarRecord.java | 327 +++ .../src/org/opensolaris/os/dtrace/StackFrame.java | 162 ++ .../opensolaris/os/dtrace/StackValueRecord.java | 104 + .../src/org/opensolaris/os/dtrace/SumValue.java | 81 + .../java/src/org/opensolaris/os/dtrace/Tuple.java | 370 ++++ .../org/opensolaris/os/dtrace/UserStackRecord.java | 327 +++ .../src/org/opensolaris/os/dtrace/Utility.java | 82 + .../src/org/opensolaris/os/dtrace/ValueRecord.java | 42 + .../src/org/opensolaris/os/dtrace/package.html | 62 + usr/src/lib/libdtrace_jni/sparc/Makefile | 33 + usr/src/lib/libdtrace_jni/sparcv9/Makefile | 34 + usr/src/lib/libdtrace_jni/spec/Makefile | 29 + usr/src/lib/libdtrace_jni/spec/Makefile.targ | 33 + usr/src/lib/libdtrace_jni/spec/amd64/Makefile | 37 + usr/src/lib/libdtrace_jni/spec/dtrace_jni.spec | 155 ++ usr/src/lib/libdtrace_jni/spec/i386/Makefile | 36 + usr/src/lib/libdtrace_jni/spec/sparc/Makefile | 36 + usr/src/lib/libdtrace_jni/spec/sparcv9/Makefile | 37 + usr/src/lib/libdtrace_jni/spec/versions | 43 + usr/src/pkgdefs/SUNWdtrc/prototype_com | 16 +- usr/src/pkgdefs/SUNWdtrc/prototype_doc | 179 ++ usr/src/pkgdefs/SUNWdtrc/prototype_i386 | 10 +- usr/src/pkgdefs/SUNWdtrc/prototype_sparc | 10 +- 93 files changed, 23105 insertions(+), 16 deletions(-) create mode 100644 usr/src/lib/libdtrace_jni/Makefile create mode 100644 usr/src/lib/libdtrace_jni/Makefile.com create mode 100644 usr/src/lib/libdtrace_jni/amd64/Makefile create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_attr.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_consume.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_error.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_jnitab.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_probe.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_util.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtj_util.h create mode 100644 usr/src/lib/libdtrace_jni/common/dtrace_jni.c create mode 100644 usr/src/lib/libdtrace_jni/common/dtrace_jni.h create mode 100644 usr/src/lib/libdtrace_jni/i386/Makefile create mode 100644 usr/src/lib/libdtrace_jni/java/Makefile create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/TestAPI.java create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/TestAPI2.java create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/TestTarget.java create mode 100755 usr/src/lib/libdtrace_jni/java/docs/examples/hello.d create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/intrstat.d create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/syscall.d create mode 100644 usr/src/lib/libdtrace_jni/java/docs/examples/target.d create mode 100644 usr/src/lib/libdtrace_jni/java/docs/html/JavaDTraceAPI.html create mode 100644 usr/src/lib/libdtrace_jni/java/docs/html/fast.html create mode 100644 usr/src/lib/libdtrace_jni/java/docs/images/JavaDTraceAPI.gif create mode 100644 usr/src/lib/libdtrace_jni/java/manifest/dtrace.mf create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/AbstractAggregationValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Aggregate.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/AggregateSpec.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Aggregation.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/AggregationRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/AggregationValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/AvgValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Consumer.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ConsumerAdapter.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ConsumerEvent.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ConsumerException.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ConsumerListener.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/CountValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/DTraceException.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/DataEvent.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Distribution.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Drop.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/DropEvent.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Error.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ErrorEvent.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ExceptionHandler.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ExitRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Flow.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/InterfaceAttributes.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/KernelStackRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LinearDistribution.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LocalConsumer.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/LogDistribution.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/MaxValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/MinValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/NativeException.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Option.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/PrintaRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/PrintfRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Probe.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProbeData.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProbeDescription.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProbeInfo.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProcessEvent.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProcessState.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Program.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ProgramInfo.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Record.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ResourceLimitException.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ScalarRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/StackFrame.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/StackValueRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/SumValue.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Tuple.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/UserStackRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/Utility.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/ValueRecord.java create mode 100644 usr/src/lib/libdtrace_jni/java/src/org/opensolaris/os/dtrace/package.html create mode 100644 usr/src/lib/libdtrace_jni/sparc/Makefile create mode 100644 usr/src/lib/libdtrace_jni/sparcv9/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/Makefile.targ create mode 100644 usr/src/lib/libdtrace_jni/spec/amd64/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/dtrace_jni.spec create mode 100644 usr/src/lib/libdtrace_jni/spec/i386/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/sparc/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/sparcv9/Makefile create mode 100644 usr/src/lib/libdtrace_jni/spec/versions create mode 100644 usr/src/pkgdefs/SUNWdtrc/prototype_doc (limited to 'usr/src') diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 02f2039133..567ef25964 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -2,9 +2,8 @@ # CDDL HEADER START # # The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License"). You may not use this file except in compliance -# with the License. +# 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. @@ -20,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -85,6 +84,7 @@ SUBDIRS += \ libadm \ libctf \ libdtrace \ + libdtrace_jni \ libcurses \ libgen \ libgss \ @@ -307,6 +307,7 @@ HDRSUBDIRS= libaio \ libdhcpsvc \ libdhcputil \ libdtrace \ + libdtrace_jni \ libeti \ libgen \ libwanboot \ @@ -431,6 +432,7 @@ libdhcpsvc: libinetutil libdhcputil: libinetutil libdladm: libdlpi libdevinfo libdtrace: libproc libgen libctf +libdtrace_jni: libuutil libdtrace libefi: libuuid $(CLOSED_BUILD)$(CLOSED)/lib/libelfsign: \ $(CLOSED)/lib/libike libcryptoutil pkcs11 diff --git a/usr/src/lib/libdtrace_jni/Makefile b/usr/src/lib/libdtrace_jni/Makefile new file mode 100644 index 0000000000..d0d83dce97 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/Makefile @@ -0,0 +1,67 @@ +# +# 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. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.lib + +SUBDIRS = $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber: java spec .WAIT $(SUBDIRS) + +install: java spec .WAIT $(SUBDIRS) + +lint: $(SUBDIRS) + +install_h: + +check: $(CHECKHDRS) + +java spec $(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +doc: + @cd java; pwd; make doc + +FRC: + +include ../Makefile.targ + +# +# Cross-reference customization: build the cross-reference only over the +# source directories, and ignore Makefiles and machine-generated source. +# +XRDIRS = common i386 sparc sparcv9 +XRDEL = Makefile* diff --git a/usr/src/lib/libdtrace_jni/Makefile.com b/usr/src/lib/libdtrace_jni/Makefile.com new file mode 100644 index 0000000000..e91ebe7a93 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/Makefile.com @@ -0,0 +1,94 @@ +# +# 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. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY = libdtrace_jni.a +VERS = .1 + +LIBSRCS = \ + dtj_util.c \ + dtj_jnitab.c \ + dtj_error.c \ + dtj_probe.c \ + dtj_attr.c \ + dtj_consume.c \ + dtrace_jni.c + +OBJECTS = $(MACHOBJS) $(LIBSRCS:%.c=%.o) + +include ../../Makefile.lib + +SRCS = $(LIBSRCS:%.c=../common/%.c) + +SRCDIR = ../common +SPECMAPFILE = $(MAPDIR)/mapfile + +CPPFLAGS += -I../common -I. +CPPFLAGS += -I$(JAVA_ROOT)/include -I$(JAVA_ROOT)/include/solaris +CPPFLAGS += -I../java/native +CFLAGS += $(CCVERBOSE) -K PIC +CFLAGS64 += $(CCVERBOSE) -K PIC +LDLIBS += -lc -luutil -ldtrace -lproc + +LINTLIB = + +LFLAGS = -t -v + +ROOTDLIBDIR = $(ROOT)/usr/lib/dtrace_jni +ROOTDLIBDIR64 = $(ROOT)/usr/lib/dtrace_jni/64 + +ROOTDLIBS = $(DLIBSRCS:%=$(ROOTDLIBDIR)/%) + +.KEEP_STATE: + +all: $(DYNLIB) + +lint: lintcheck + +%.o: ../common/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +$(ROOTDLIBDIR): + $(INS.dir) + +$(ROOTDLIBDIR64): $(ROOTDLIBDIR) + $(INS.dir) + +$(ROOTDLIBDIR)/%.o: %.o + $(INS.file) + +$(ROOTDLIBDIR64)/%.o: %.o + $(INS.file) + +$(ROOTDLIBS): $(ROOTDLIBDIR) + +$(ROOTDOBJS): $(ROOTDLIBDIR) + +$(ROOTDOBJS64): $(ROOTDLIBDIR64) + +include ../../Makefile.targ diff --git a/usr/src/lib/libdtrace_jni/amd64/Makefile b/usr/src/lib/libdtrace_jni/amd64/Makefile new file mode 100644 index 0000000000..6755e8b390 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/amd64/Makefile @@ -0,0 +1,34 @@ +# +# 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. +# +# ident "%Z%%M% %I% %E% SMI" +# + +MAPDIR = ../spec/amd64 + +include ../Makefile.com +include ../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/lib/libdtrace_jni/common/dtj_attr.c b/usr/src/lib/libdtrace_jni/common/dtj_attr.c new file mode 100644 index 0000000000..83edf12843 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_attr.c @@ -0,0 +1,181 @@ +/* + * 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 + +/* + * This file creates instances of the Java class + * org.opensolaris.os.dtrace.InterfaceAttributes. + */ + +static const char * +dtj_stability_name(dtrace_stability_t stability) +{ + const char *name; + switch (stability) { + case DTRACE_STABILITY_INTERNAL: + name = "INTERNAL"; + break; + case DTRACE_STABILITY_PRIVATE: + name = "PRIVATE"; + break; + case DTRACE_STABILITY_OBSOLETE: + name = "OBSOLETE"; + break; + case DTRACE_STABILITY_EXTERNAL: + name = "EXTERNAL"; + break; + case DTRACE_STABILITY_UNSTABLE: + name = "UNSTABLE"; + break; + case DTRACE_STABILITY_EVOLVING: + name = "EVOLVING"; + break; + case DTRACE_STABILITY_STABLE: + name = "STABLE"; + break; + case DTRACE_STABILITY_STANDARD: + name = "STANDARD"; + break; + default: + name = NULL; + } + + return (name); +} + +static const char * +dtj_dependency_class_name(dtrace_class_t class) +{ + const char *name; + switch (class) { + case DTRACE_CLASS_UNKNOWN: + name = "UNKNOWN"; + break; + case DTRACE_CLASS_CPU: + name = "CPU"; + break; + case DTRACE_CLASS_PLATFORM: + name = "PLATFORM"; + break; + case DTRACE_CLASS_GROUP: + name = "GROUP"; + break; + case DTRACE_CLASS_ISA: + name = "ISA"; + break; + case DTRACE_CLASS_COMMON: + name = "COMMON"; + break; + default: + name = NULL; + } + + return (name); +} + +jobject +dtj_new_attribute(dtj_java_consumer_t *jc, const dtrace_attribute_t *attr) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + const char *name; + + jstring jname = NULL; + jobject jattr = NULL; /* return value */ + + jattr = (*jenv)->NewObject(jenv, g_attr_jc, g_attrinit_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + + /* name stability */ + name = dtj_stability_name(attr->dtat_name); + if (!name) { + dtj_throw_illegal_argument(jenv, + "unexpected name stability value: %d", + attr->dtat_name); + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + jname = (*jenv)->NewStringUTF(jenv, name); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + (*jenv)->CallVoidMethod(jenv, jattr, g_attrset_name_jm, jname); + (*jenv)->DeleteLocalRef(jenv, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + + /* data stability */ + name = dtj_stability_name(attr->dtat_data); + if (!name) { + dtj_throw_illegal_argument(jenv, + "unexpected data stability value: %d", + attr->dtat_data); + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + jname = (*jenv)->NewStringUTF(jenv, name); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + (*jenv)->CallVoidMethod(jenv, jattr, g_attrset_data_jm, jname); + (*jenv)->DeleteLocalRef(jenv, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + + /* dependency class */ + name = dtj_dependency_class_name(attr->dtat_class); + if (!name) { + dtj_throw_illegal_argument(jenv, + "unexpected dependency class value: %d", + attr->dtat_class); + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + jname = (*jenv)->NewStringUTF(jenv, name); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + (*jenv)->CallVoidMethod(jenv, jattr, g_attrset_class_jm, jname); + (*jenv)->DeleteLocalRef(jenv, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jattr); + return (NULL); + } + + return (jattr); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_consume.c b/usr/src/lib/libdtrace_jni/common/dtj_consume.c new file mode 100644 index 0000000000..026bbea5a3 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_consume.c @@ -0,0 +1,2247 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Implements the work done in the running consumer loop. The native Java + * methods (JNI layer) are implemented in dtrace_jni.c. + */ + +/* Record handler passed to dtrace_work() */ +static int dtj_chewrec(const dtrace_probedata_t *, const dtrace_recdesc_t *, + void *); +/* Probe data handler passed to dtrace_work() */ +static int dtj_chew(const dtrace_probedata_t *, void *); + +/* Processes requests from LocalConsumer enqueued during dtrace_sleep() */ +static dtj_status_t dtj_process_requests(dtj_java_consumer_t *); + +/* + * Callback handlers set in dtj_set_callback_handlers(), called from libdtrace + * in the consumer loop (from dtrace_work()) + */ +static int dtj_drophandler(const dtrace_dropdata_t *, void *); +static int dtj_errhandler(const dtrace_errdata_t *, void *); +static void dtj_prochandler(struct ps_prochandle *, const char *, void *); +static int dtj_setopthandler(const dtrace_setoptdata_t *, void *); +/* + * Buffered output handler called from libdtrace in both the consumer loop (from + * dtrace_work()) and the get_aggregate() function (from + * dtrace_aggregate_print()). + */ +static int dtj_bufhandler(const dtrace_bufdata_t *, void *); + +/* Conversion of libdtrace data into Java Objects */ +static jobject dtj_recdata(dtj_java_consumer_t *, uint32_t, caddr_t); +static jobject dtj_bytedata(JNIEnv *, uint32_t, caddr_t); +static jobject dtj_new_stack_record(const caddr_t, const dtrace_recdesc_t *, + dtj_java_consumer_t *); +static jobject dtj_new_probedata_stack_record(const dtrace_probedata_t *, + const dtrace_recdesc_t *, dtj_java_consumer_t *); +/* Aggregation data */ +static jobject dtj_new_tuple_stack_record(const dtrace_aggdata_t *, + const dtrace_recdesc_t *, const char *, dtj_java_consumer_t *); +static jobject dtj_new_distribution(const dtrace_aggdata_t *, + const dtrace_recdesc_t *, dtj_java_consumer_t *); +static jobject dtj_new_aggval(dtj_java_consumer_t *, const dtrace_aggdata_t *, + const dtrace_recdesc_t *); +static int64_t dtj_average(caddr_t, uint64_t); +static int64_t dtj_avg_total(caddr_t, uint64_t); +static int64_t dtj_avg_count(caddr_t); + +/* Aggregation functions */ +static void dtj_aggwalk_init(dtj_java_consumer_t *); +static int dtj_agghandler(const dtrace_bufdata_t *, dtj_java_consumer_t *); +static boolean_t dtj_is_included(const dtrace_aggdata_t *, + dtj_java_consumer_t *); +static void dtj_attach_frames(dtj_java_consumer_t *, jobject, + jobjectArray); +static boolean_t dtj_is_stack_action(dtrace_actkind_t); +static int dtj_clear(const dtrace_aggdata_t *, void *); + +/* + * The consumer loop needs to protect calls to libdtrace functions with a global + * lock. JNI native method calls in dtrace_jni.c are already protected and do + * not need this function. + */ +dtj_status_t +dtj_get_dtrace_error(dtj_java_consumer_t *jc, dtj_error_t *e) +{ + JNIEnv *jenv = jc->dtjj_jenv; + dtrace_hdl_t *dtp = jc->dtjj_consumer->dtjc_dtp; + + /* Grab global lock */ + (*jenv)->MonitorEnter(jenv, g_caller_jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTJ_ERR); + } + e->dtje_number = dtrace_errno(dtp); + e->dtje_message = dtrace_errmsg(dtp, e->dtje_number); + (*jenv)->MonitorExit(jenv, g_caller_jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTJ_ERR); + } + return (DTJ_OK); +} + +/* + * Protected by global lock (LocalConsumer.class) that protects call to + * Java_org_opensolaris_os_dtrace_LocalConsumer__1go() + */ +dtj_status_t +dtj_set_callback_handlers(dtj_java_consumer_t *jc) +{ + dtrace_hdl_t *dtp = jc->dtjj_consumer->dtjc_dtp; + dtrace_optval_t optval; + + /* + * The user argument to the bufhandler is the lookup key used to obtain + * the thread-specific java consumer. The java consumer contains JNI + * state specific to either the consumer loop or the getAggregate() + * call. + */ + if (dtrace_handle_buffered(dtp, &dtj_bufhandler, NULL) == -1) { + dtj_throw_dtrace_exception(jc, + "failed to establish buffered handler: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + if (dtrace_handle_drop(dtp, &dtj_drophandler, NULL) == -1) { + dtj_throw_dtrace_exception(jc, + "failed to establish drop handler: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + if (dtrace_handle_err(dtp, &dtj_errhandler, NULL) == -1) { + dtj_throw_dtrace_exception(jc, + "failed to establish error handler: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + if (dtrace_handle_proc(dtp, &dtj_prochandler, NULL) == -1) { + dtj_throw_dtrace_exception(jc, + "failed to establish proc handler: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + if (dtrace_getopt(dtp, "flowindent", &optval) == -1) { + dtj_throw_dtrace_exception(jc, + "couldn't get option %s: %s", "flowindent", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + jc->dtjj_consumer->dtjc_flow = (optval != DTRACEOPT_UNSET); + + if (dtrace_handle_setopt(dtp, &dtj_setopthandler, NULL) == -1) { + dtj_throw_dtrace_exception(jc, + "failed to establish setopt handler: %s", + dtrace_errmsg(dtp, dtrace_errno(dtp))); + return (DTJ_ERR); + } + + return (DTJ_OK); +} + +static int +/* ARGSUSED */ +dtj_drophandler(const dtrace_dropdata_t *data, void *arg) +{ + dtj_java_consumer_t *jc; + JNIEnv *jenv; + + const char *dropkind; + + jstring msg = NULL; + jstring kind = NULL; + jobject drop = NULL; + + jc = pthread_getspecific(g_dtj_consumer_key); + jenv = jc->dtjj_jenv; + + msg = dtj_NewStringNative(jenv, data->dtdda_msg); + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + switch (data->dtdda_kind) { + case DTRACEDROP_PRINCIPAL: + dropkind = "PRINCIPAL"; + break; + case DTRACEDROP_AGGREGATION: + dropkind = "AGGREGATION"; + break; + case DTRACEDROP_DYNAMIC: + dropkind = "DYNAMIC"; + break; + case DTRACEDROP_DYNRINSE: + dropkind = "DYNRINSE"; + break; + case DTRACEDROP_DYNDIRTY: + dropkind = "DYNDIRTY"; + break; + case DTRACEDROP_SPEC: + dropkind = "SPEC"; + break; + case DTRACEDROP_SPECBUSY: + dropkind = "SPECBUSY"; + break; + case DTRACEDROP_SPECUNAVAIL: + dropkind = "SPECUNAVAIL"; + break; + case DTRACEDROP_STKSTROVERFLOW: + dropkind = "STKSTROVERFLOW"; + break; + case DTRACEDROP_DBLERROR: + dropkind = "DBLERROR"; + break; + default: + dropkind = "UNKNOWN"; + } + kind = (*jenv)->NewStringUTF(jenv, dropkind); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, msg); + return (DTRACE_HANDLE_ABORT); + } + drop = (*jenv)->NewObject(jenv, g_drop_jc, g_dropinit_jm, + data->dtdda_cpu, kind, data->dtdda_drops, data->dtdda_total, msg); + (*jenv)->DeleteLocalRef(jenv, kind); + (*jenv)->DeleteLocalRef(jenv, msg); + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, g_drop_jm, drop); + (*jenv)->DeleteLocalRef(jenv, drop); + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + + return (DTRACE_HANDLE_OK); +} + +static int +/* ARGSUSED */ +dtj_errhandler(const dtrace_errdata_t *data, void *arg) +{ + dtj_java_consumer_t *jc; + JNIEnv *jenv; + + const char *f; + int64_t addr; + + jobject probe = NULL; + jstring fault = NULL; + jstring msg = NULL; + jobject error = NULL; + + jc = pthread_getspecific(g_dtj_consumer_key); + jenv = jc->dtjj_jenv; + + probe = dtj_new_probedesc(jc, data->dteda_pdesc); + if (!probe) { + return (DTRACE_HANDLE_ABORT); + } + f = dtj_get_fault_name(data->dteda_fault); + if (f) { + fault = (*jenv)->NewStringUTF(jenv, f); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, probe); + return (DTRACE_HANDLE_ABORT); + } + } + switch (data->dteda_fault) { + case DTRACEFLT_BADADDR: + case DTRACEFLT_BADALIGN: + addr = data->dteda_addr; + break; + default: + addr = -1; + } + msg = dtj_NewStringNative(jenv, data->dteda_msg); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, probe); + (*jenv)->DeleteLocalRef(jenv, fault); + return (DTRACE_HANDLE_ABORT); + } + error = (*jenv)->NewObject(jenv, g_error_jc, g_errinit_jm, + probe, + data->dteda_edesc->dtepd_epid, + data->dteda_cpu, + data->dteda_action, + data->dteda_offset, + fault, addr, msg); + (*jenv)->DeleteLocalRef(jenv, msg); + (*jenv)->DeleteLocalRef(jenv, fault); + (*jenv)->DeleteLocalRef(jenv, probe); + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, g_error_jm, error); + (*jenv)->DeleteLocalRef(jenv, error); + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + + return (DTRACE_HANDLE_OK); +} + +/* + * Since the function signature does not allow us to return an abort signal, we + * need to temporarily clear any pending exception before returning, since + * without the abort we can't guarantee that the exception will be checked in + * time to prevent invalid JNI function calls. + */ +static void +/* ARGSUSED */ +dtj_prochandler(struct ps_prochandle *P, const char *msg, void *arg) +{ + dtj_java_consumer_t *jc; + JNIEnv *jenv; + + const psinfo_t *prp = Ppsinfo(P); + int pid = Pstatus(P)->pr_pid; + int signal = -1; + char signame[SIG2STR_MAX]; + const char *statusname; + int exit = INT_MAX; /* invalid initial status */ + + jstring status = NULL; + jstring signalName = NULL; + jstring message = NULL; + jobject process = NULL; + + jc = pthread_getspecific(g_dtj_consumer_key); + jenv = jc->dtjj_jenv; + + switch (Pstate(P)) { + case PS_RUN: + statusname = "RUN"; + break; + case PS_STOP: + statusname = "STOP"; + break; + case PS_UNDEAD: + statusname = "UNDEAD"; + if (prp != NULL) { + exit = WEXITSTATUS(prp->pr_wstat); + } + if (prp != NULL && WIFSIGNALED(prp->pr_wstat)) { + signal = WTERMSIG(prp->pr_wstat); + (void) proc_signame(signal, signame, sizeof (signame)); + signalName = (*jenv)->NewStringUTF(jenv, signame); + if ((*jenv)->ExceptionCheck(jenv)) { + goto proc_end; + } + } + ++jc->dtjj_consumer->dtjc_procs_ended; + break; + case PS_LOST: + statusname = "LOST"; + ++jc->dtjj_consumer->dtjc_procs_ended; + break; + case PS_DEAD: + /* + * PS_DEAD not handled by dtrace.c prochandler, still this is a + * case of process termination and it can't hurt to handle it. + */ + statusname = "DEAD"; + ++jc->dtjj_consumer->dtjc_procs_ended; + break; + default: + /* + * Unexpected, but erring on the side of tolerance by not + * crashing the consumer. Failure to notify listeners of + * process state not handled by the dtrace.c prochandler does + * not seem serious. + */ + return; + } + + status = (*jenv)->NewStringUTF(jenv, statusname); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, signalName); + goto proc_end; + } + if (msg) { + message = dtj_NewStringNative(jenv, msg); + if (!message) { + (*jenv)->DeleteLocalRef(jenv, status); + (*jenv)->DeleteLocalRef(jenv, signalName); + goto proc_end; + } + } + process = (*jenv)->NewObject(jenv, g_process_jc, g_procinit_jm, + pid, status, signal, signalName, NULL, message); + (*jenv)->DeleteLocalRef(jenv, status); + (*jenv)->DeleteLocalRef(jenv, signalName); + (*jenv)->DeleteLocalRef(jenv, message); + if ((*jenv)->ExceptionCheck(jenv)) { + goto proc_end; + } + if (exit != INT_MAX) { + /* valid exit status */ + (*jenv)->CallVoidMethod(jenv, process, g_procexit_jm, exit); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, process); + goto proc_end; + } + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, g_proc_jm, process); + (*jenv)->DeleteLocalRef(jenv, process); + +proc_end: + + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Save the exception so we can rethrow it later when it's safe. + */ + if (!jc->dtjj_exception) { + jthrowable e = (*jenv)->ExceptionOccurred(jenv); + jc->dtjj_exception = e; + } + (*jenv)->ExceptionClear(jenv); + } +} + +static int +/* ARGSUSED */ +dtj_setopthandler(const dtrace_setoptdata_t *data, void *arg) +{ + dtj_java_consumer_t *jc; + + jc = pthread_getspecific(g_dtj_consumer_key); + if (strcmp(data->dtsda_option, "flowindent") == 0) { + jc->dtjj_consumer->dtjc_flow = + (data->dtsda_newval != DTRACEOPT_UNSET); + } + return (DTRACE_HANDLE_OK); +} + +/* + * Most of this function lifted from libdtrace/common/dt_consume.c + * dt_print_bytes(). + */ +static jobject +dtj_bytedata(JNIEnv *jenv, uint32_t nbytes, caddr_t addr) +{ + /* + * If the byte stream is a series of printable characters, followed by + * a terminating byte, we print it out as a string. Otherwise, we + * assume that it's something else and just print the bytes. + */ + int i, j; + char *c = addr; + + jobject jobj = NULL; /* return value */ + + if (nbytes == 0) { + return ((*jenv)->NewStringUTF(jenv, "")); + } + + for (i = 0; i < nbytes; i++) { + /* + * We define a "printable character" to be one for which + * isprint(3C) returns non-zero, isspace(3C) returns non-zero, + * or a character which is either backspace or the bell. + * Backspace and the bell are regrettably special because + * they fail the first two tests -- and yet they are entirely + * printable. These are the only two control characters that + * have meaning for the terminal and for which isprint(3C) and + * isspace(3C) return 0. + */ + if (isprint(c[i]) || isspace(c[i]) || + c[i] == '\b' || c[i] == '\a') + continue; + + if (c[i] == '\0' && i > 0) { + /* + * This looks like it might be a string. Before we + * assume that it is indeed a string, check the + * remainder of the byte range; if it contains + * additional non-nul characters, we'll assume that + * it's a binary stream that just happens to look like + * a string. + */ + for (j = i + 1; j < nbytes; j++) { + if (c[j] != '\0') + break; + } + + if (j != nbytes) + break; + + /* It's a string */ + return (dtj_NewStringNative(jenv, (char *)addr)); + } + + break; + } + + if (i == nbytes) { + /* + * The byte range is all printable characters, but there is + * no trailing nul byte. We'll assume that it's a string. + */ + char *s = malloc(nbytes + 1); + if (!s) { + dtj_throw_out_of_memory(jenv, + "failed to allocate string value"); + return (NULL); + } + (void) strncpy(s, c, nbytes); + s[nbytes] = '\0'; + jobj = dtj_NewStringNative(jenv, s); + free(s); + return (jobj); + } + + /* return byte array */ + jobj = (*jenv)->NewByteArray(jenv, nbytes); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + (*jenv)->SetByteArrayRegion(jenv, (jbyteArray)jobj, 0, nbytes, + (const jbyte *)c); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jobj); + return (NULL); + } + return (jobj); +} + +/* + * Return NULL if memory could not be allocated (OutOfMemoryError is thrown in + * that case). + */ +static jobject +dtj_recdata(dtj_java_consumer_t *jc, uint32_t size, caddr_t addr) +{ + JNIEnv *jenv = jc->dtjj_jenv; + jobject jobj; + + switch (size) { + case 1: + jobj = (*jenv)->NewObject(jenv, g_byte_jc, + g_byteinit_jm, *((char *)addr)); + break; + case 2: + jobj = (*jenv)->NewObject(jenv, g_short_jc, + /* LINTED - alignment */ + g_shortinit_jm, *((int16_t *)addr)); + break; + case 4: + jobj = (*jenv)->NewObject(jenv, g_int_jc, + /* LINTED - alignment */ + g_intinit_jm, *((int32_t *)addr)); + break; + case 8: + jobj = (*jenv)->NewObject(jenv, g_long_jc, + /* LINTED - alignment */ + g_longinit_jm, *((int64_t *)addr)); + break; + default: + jobj = dtj_bytedata(jenv, size, addr); + break; + } + + return (jobj); +} + +/* + * This is the record handling function passed to dtrace_work(). It differs + * from the bufhandler registered with dtrace_handle_buffered() as follows: + * + * 1. It does not have access to libdtrace formatted output. + * 2. It is called once for every D program statement, not for every + * output-producing D action or aggregation record. A statement may be a + * variable assignment, having no size and producing no output. + * 3. It is called for the D exit() action; the bufhandler is not. + * 4. In response to the printa() action, it is called with a record having an + * action of type DTRACEACT_PRINTA. The bufhandler never sees that action + * value. It only sees the output-producing aggregation records. + * 5. It is called with a NULL record at the end of each probedata. + */ +static int +dtj_chewrec(const dtrace_probedata_t *data, const dtrace_recdesc_t *rec, + void *arg) +{ + dtj_java_consumer_t *jc = arg; + JNIEnv *jenv = jc->dtjj_jenv; + + const dtrace_eprobedesc_t *edesc = data->dtpda_edesc; + dtrace_actkind_t act; + int r; + + /* + * Update the record index to that of the current record, or to that of + * the last record if rec is NULL (signalling end of probe data). + */ + if (rec == NULL) { + r = edesc->dtepd_nrecs; /* end of probe data */ + } else { + /* + * This record handler is called once for the printf() action, + * but there may be multiple records in the probedata + * corresponding to the unformatted elements of that printf(). + * We don't know ahead of time how many probedata records + * libdtrace will consume to produce output for one printf() + * action, so we look back at the previous call to dtj_chewrec() + * to see how many probedata records were consumed. All + * non-null elements in the range from the previous record index + * up to and not including the current record index are assumed + * to be unformatted printf() elements, and will be attached to + * the PrintfRecord from the previous call. A null element in + * that range is the result of a D program statement preceding + * the printf() that is not a D action. These generate + * probedata records accounted for by the null placeholder, but + * do not advance the probedata offset and are not part of the + * subsequent printf(). + * + * If rec->dtrd_size == 0, the record represents a D program + * statement that is not a D action. It has no size and does + * not advance the offset in the probedata. Handle it normally + * without special-casing or premature return, since in all + * cases we look at the previous record later in this function. + */ + for (r = jc->dtjj_consumer->dtjc_probedata_rec_i; + ((r < edesc->dtepd_nrecs) && + (edesc->dtepd_rec[r].dtrd_offset < rec->dtrd_offset)); + ++r) { + } + } + + /* + * Attach the Java representations of the libdtrace data elements + * pertaining to the previous call to this record handler to the + * previous Java Record. (All data elements belonging to the current + * probedata are added to a single list by the probedata consumer + * function dtj_chew() before this record consumer function is ever + * called.) For example, if the previous Record was generated by the + * printf() action, and dtj_chew() listed 3 records for its 3 + * unformatted elements, those 3 libdtrace records comprise 1 + * PrintfRecord. Note that we cannot know how many data elements apply + * to the current rec until we find out the data index where the next + * rec starts. (The knowledge of how many probedata records to consume + * is private to libdtrace.) + */ + if (jc->dtjj_consumer->dtjc_probedata_act == DTRACEACT_PRINTF) { + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataattach_jm, + jc->dtjj_consumer->dtjc_probedata_rec_i, r - 1); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + } + + if (rec == NULL) { + /* + * End of probe data. Notify listeners of the new ProbeData + * instance. + */ + if (jc->dtjj_probedata) { + /* previous probedata */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataclear_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, + g_pdatanext_jm, jc->dtjj_probedata); + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_probedata); + jc->dtjj_probedata = NULL; + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Do not wrap exception thrown from + * ConsumerListener. + */ + return (DTRACE_CONSUME_ABORT); + } + } + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_printa_buffer); + jc->dtjj_printa_buffer = NULL; + return (DTRACE_CONSUME_NEXT); + } + + act = rec->dtrd_action; + + /* Set previous record action and data index to current */ + jc->dtjj_consumer->dtjc_probedata_act = act; + jc->dtjj_consumer->dtjc_probedata_rec_i = r; + + switch (act) { + case DTRACEACT_DIFEXPR: + if (rec->dtrd_size == 0) { + /* + * The current record is not a D action, but a program + * statement such as a variable assignment, not to be + * confused with the trace() action. + */ + break; + } + /* + * Add a Record for the trace() action that references the + * native probedata element listed at the current index. + */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_trace_jm, + jc->dtjj_consumer->dtjc_probedata_rec_i); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + break; + case DTRACEACT_PRINTF: + /* + * Just add an empty PrintfRecord for now. We'll attach the + * unformatted elements in a subsequent call to this function. + * (We don't know how many there will be.) + */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_printf_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + /* defer formatted string to dtj_bufhandler() */ + break; + case DTRACEACT_PRINTA: { + jobject jbuf = NULL; + + dtj_aggwalk_init(jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_printa_jm, + jc->dtjj_consumer->dtjc_printa_snaptime, + (rec->dtrd_format != 0)); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + if (jc->dtjj_printa_buffer == NULL) { + /* + * Create a StringBuffer to collect the pieces of + * formatted output into a single String. + */ + jbuf = (*jenv)->NewObject(jenv, g_buf_jc, + g_bufinit_jm); + if (!jbuf) { + /* OutOfMemoryError pending */ + return (DTRACE_CONSUME_ABORT); + } + jc->dtjj_printa_buffer = jbuf; + } + /* defer aggregation records to dtj_bufhandler() */ + break; + } + case DTRACEACT_EXIT: + /* + * Add a Record for the exit() action that references the native + * probedata element listed at the current index. + */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_exit_jm, + jc->dtjj_consumer->dtjc_probedata_rec_i); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + return (DTRACE_CONSUME_NEXT); + } + + return (DTRACE_CONSUME_THIS); +} + +/* + * This is the probe handling function passed to dtrace_work(). It is is called + * once every time a probe fires. It is the first of all the callbacks for the + * current probe. It is followed by multiple callbacks to dtj_chewrec(), one + * for each probedata record. Each call to dtj_chewrec() is followed by zero or + * more callbacks to the bufhandler, one for each output-producing action or + * aggregation record. + */ +static int +dtj_chew(const dtrace_probedata_t *data, void *arg) +{ + dtj_java_consumer_t *jc = arg; + JNIEnv *jenv = jc->dtjj_jenv; + + dtrace_eprobedesc_t *edesc; + dtrace_probedesc_t *pdesc; + dtrace_recdesc_t *rec; + int epid; + int cpu; + int nrecs; + int i; + + jobject jpdata = NULL; + jobject jprobe = NULL; + jobject jflow = NULL; + jstring jflowkind = NULL; + jobject jobj = NULL; + + edesc = data->dtpda_edesc; + epid = (int)edesc->dtepd_epid; + pdesc = data->dtpda_pdesc; + cpu = (int)data->dtpda_cpu; + if ((jprobe = dtj_new_probedesc(jc, pdesc)) == NULL) { + /* java exception pending */ + return (DTRACE_CONSUME_ABORT); + } + nrecs = edesc->dtepd_nrecs; + + if (jc->dtjj_consumer->dtjc_flow) { + const char *kind; + switch (data->dtpda_flow) { + case DTRACEFLOW_ENTRY: + kind = "ENTRY"; + break; + case DTRACEFLOW_RETURN: + kind = "RETURN"; + break; + case DTRACEFLOW_NONE: + kind = "NONE"; + break; + default: + kind = NULL; + } + if (kind != NULL) { + int depth; + jflowkind = (*jenv)->NewStringUTF(jenv, kind); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jprobe); + return (DTRACE_CONSUME_ABORT); + } + /* + * Use the knowledge that libdtrace indents 2 spaces per + * level in the call stack to calculate the depth. + */ + depth = (data->dtpda_indent / 2); + jflow = (*jenv)->NewObject(jenv, g_flow_jc, + g_flowinit_jm, jflowkind, depth); + (*jenv)->DeleteLocalRef(jenv, jflowkind); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jprobe); + return (DTRACE_CONSUME_ABORT); + } + } + } + + /* Create ProbeData instance */ + jpdata = (*jenv)->NewObject(jenv, g_pdata_jc, g_pdatainit_jm, + epid, cpu, jprobe, jflow, nrecs); + (*jenv)->DeleteLocalRef(jenv, jprobe); + (*jenv)->DeleteLocalRef(jenv, jflow); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + + /* + * Populate the ProbeData list of Java data elements in advance so we + * don't need to peek back in the record handler at libdtrace records + * that have already been consumed. In the Java API, each ProbeData + * Record is generated by one D action, while in the native libdtrace + * there may be more than one probedata record (each a single data + * element) per D action. For example PrintfRecord has multiple + * unformatted elements, each represented by a native probedata record, + * but combined by the API into a single PrintfRecord. + */ + for (i = 0; i < nrecs; ++i) { + rec = &edesc->dtepd_rec[i]; + /* + * A statement that is not a D action, such as assignment to a + * variable, has no size. Add a NULL placeholder to the scratch + * list of Java probedata elements in that case. + */ + jobj = NULL; /* initialize object reference to null */ + if (rec->dtrd_size > 0) { + if (dtj_is_stack_action(rec->dtrd_action)) { + jobj = dtj_new_probedata_stack_record(data, + rec, jc); + } else { + jobj = dtj_recdata(jc, rec->dtrd_size, + (data->dtpda_data + rec->dtrd_offset)); + } + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jpdata); + return (DTRACE_CONSUME_ABORT); + } + } + + (*jenv)->CallVoidMethod(jenv, jpdata, g_pdataadd_jm, jobj); + (*jenv)->DeleteLocalRef(jenv, jobj); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jpdata); + return (DTRACE_CONSUME_ABORT); + } + } + + if (jc->dtjj_probedata != NULL) { + dtj_throw_illegal_state(jenv, "unfinished probedata"); + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jpdata); + return (DTRACE_CONSUME_ABORT); + } + jc->dtjj_probedata = jpdata; + + /* Initialize per-consumer probedata fields */ + jc->dtjj_consumer->dtjc_probedata_rec_i = 0; + jc->dtjj_consumer->dtjc_probedata_act = DTRACEACT_NONE; + dtj_aggwalk_init(jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_CONSUME_ABORT); + } + + return (DTRACE_CONSUME_THIS); +} + +/* + * This is the buffered output handler registered with dtrace_handle_buffered(). + * It's purpose is to make the output of the libdtrace print routines available + * to this API, without writing any of it to a file (such as stdout). This is + * needed for the stack(), ustack(), and jstack() actions to get human-readable + * stack values, since there is no public function in libdtrace to convert stack + * values to strings. It is also used to get the formatted output of the D + * printf() and printa() actions. + * + * The bufhandler is called once for each output-producing, non-aggregating D + * action, such as trace() or printf(), and once for each libdtrace aggregation + * record (whether in response to the D printa() action, or the Consumer + * getAggregate() method). In the simple printa() case that takes one + * aggregation and does not specify a format string, there is one libdtrace + * record per tuple element plus one for the corresponding value. The complete + * tuple/value pair becomes a single AggregationRecord exported by the API. + * When multiple aggregations are passed to printa(), each tuple is associated + * with a list of values, one from each aggregation. If a printa() format + * string does not specify placeholders for every aggregation value and tuple + * member, callbacks for those values and tuple members are omitted (and the + * data is omitted from the resulting PrintaRecord). + * + * Notes to characterize some non-obvious bufhandler behavior: + * + * 1. dtj_bufhandler() is never called with bufdata->dtbda_recdesc->dtrd_action + * DTRACEACT_PRINTA. That action only appears in the probedata consumer + * functions dtj_chew() and dtj_chewrec() before the bufhandler is called with + * subsequent aggregation records. + * + * 2. If printa() specifies a format string argument, then the bufhandler is + * called only for those elements of the tuple/value pair that are included in + * the format string. If a stack() tuple member is omitted from the format + * string, its human-readable representation will not be available to this API, + * so the stack frame array is also omitted from the resulting + * AggregationRecord. The bufhandler is also called once for each string of + * characters surrounding printa() format string placeholders. For example, + * " %@d %d stack%k\n" results in the following callbacks: + * - two spaces + * - the aggregation value + * - a single space + * - the first tuple member (an integer) + * - " stack" + * - the second tuple member (a stack) + * - a newline + * A NULL record (NULL dtbda_recdesc) distinguishes a callback with interstitial + * format string characters from a callback with a tuple member or aggregation + * value (which has a non-NULL recdesc). The contents are also distinguished by + * the following flags: + * DTRACE_BUFDATA_AGGKEY + * DTRACE_BUFDATA_AGGVAL + * DTRACE_BUFDATA_AGGFORMAT + * DTRACE_BUFDATA_AGGLAST + * + * There is no final callback with the complete formatted string, so that must + * be concatenated across multiple callbacks to the bufhandler. + * + * 3. bufdata->dtbda_probe->dtpda_data may be overwritten by libdtrace print + * routines. The address is cached in the dtj_chew() function in case it is + * needed in the bufhandler. + */ +static int +/* ARGSUSED */ +dtj_bufhandler(const dtrace_bufdata_t *bufdata, void *arg) +{ + dtj_java_consumer_t *jc; + JNIEnv *jenv; + const dtrace_recdesc_t *rec; + dtrace_actkind_t act = DTRACEACT_NONE; + const char *s; + + jobject jstr = NULL; + + /* + * Get the thread-specific java consumer. The bufhandler needs access + * to the correct JNI state specific to either the consumer loop or the + * getAggregate() call (aggregation snapshots can be requested + * asynchronously while the consumer loop generates PrintaRecords in + * dtrace_work() for ConsumerListeners). + */ + jc = pthread_getspecific(g_dtj_consumer_key); + jenv = jc->dtjj_jenv; + + /* + * In at least one corner case (printa with multiple aggregations and a + * format string that does not completely specify the tuple), returning + * DTRACE_HANDLE_ABORT does not prevent a subsequent callback to this + * bufhandler. This check ensures that the invalid call is ignored. + */ + if ((*jenv)->ExceptionCheck(jenv)) { + return (DTRACE_HANDLE_ABORT); + } + + if (bufdata->dtbda_aggdata) { + return (dtj_agghandler(bufdata, jc)); + } + + s = bufdata->dtbda_buffered; + if (s == NULL) { + return (DTRACE_HANDLE_OK); + } + + rec = bufdata->dtbda_recdesc; + if (rec) { + act = rec->dtrd_action; + } + + switch (act) { + case DTRACEACT_DIFEXPR: + /* trace() action */ + break; + case DTRACEACT_PRINTF: + /* + * Only the formatted string was not available to dtj_chewrec(), + * so we attach that now. + */ + jstr = dtj_NewStringNative(jenv, s); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataset_formatted_jm, jstr); + (*jenv)->DeleteLocalRef(jenv, jstr); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + break; + case DTRACEACT_STACK: + case DTRACEACT_USTACK: + case DTRACEACT_JSTACK: + /* stand-alone stack(), ustack(), or jstack() action */ + jstr = (*jenv)->NewStringUTF(jenv, s); + if (!jstr) { + /* OutOfMemoryError pending */ + return (DTRACE_HANDLE_ABORT); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_stack_jm, + jc->dtjj_consumer->dtjc_probedata_rec_i, jstr); + (*jenv)->DeleteLocalRef(jenv, jstr); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + break; + default: + /* + * The record handler dtj_chewrec() defers nothing else to this + * bufhandler. + */ + break; + } + + return (DTRACE_HANDLE_OK); +} + +static boolean_t +dtj_is_stack_action(dtrace_actkind_t act) +{ + boolean_t stack_action; + switch (act) { + case DTRACEACT_STACK: + case DTRACEACT_USTACK: + case DTRACEACT_JSTACK: + stack_action = B_TRUE; + break; + default: + stack_action = B_FALSE; + } + return (stack_action); +} + +/* + * Called by get_aggregate() to clear only those aggregations specified by the + * caller. + */ +static int +dtj_clear(const dtrace_aggdata_t *data, void *arg) +{ + dtj_java_consumer_t *jc = arg; + jboolean cleared = JNI_FALSE; + + jstring jname = NULL; + + if (jc->dtjj_aggregate_spec) { + JNIEnv *jenv = jc->dtjj_jenv; + + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + + jname = (*jenv)->NewStringUTF(jenv, aggdesc->dtagd_name); + if (!jname) { + /* java exception pending */ + return (DTRACE_AGGWALK_ABORT); + } + + cleared = (*jenv)->CallBooleanMethod(jenv, + jc->dtjj_aggregate_spec, g_aggspec_cleared_jm, jname); + (*jenv)->DeleteLocalRef(jenv, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_AGGWALK_ABORT); + } + } + + return (cleared ? DTRACE_AGGWALK_CLEAR : DTRACE_AGGWALK_NEXT); +} + +static int64_t +dtj_average(caddr_t addr, uint64_t normal) +{ + /* LINTED - alignment */ + uint64_t *data = (uint64_t *)addr; + + return (data[0] ? + (long long)(data[1] / normal / data[0]) : 0); +} + +static int64_t +dtj_avg_total(caddr_t addr, uint64_t normal) +{ + /* LINTED - alignment */ + uint64_t *data = (uint64_t *)addr; + + return ((long long)(data[1] / normal)); +} + +static int64_t +dtj_avg_count(caddr_t addr) +{ + /* LINTED - alignment */ + uint64_t *data = (uint64_t *)addr; + + return ((long long)data[0]); +} + +static jobject +dtj_new_probedata_stack_record(const dtrace_probedata_t *data, + const dtrace_recdesc_t *rec, dtj_java_consumer_t *jc) +{ + caddr_t addr; + + /* Get raw stack data */ + addr = data->dtpda_data + rec->dtrd_offset; + return (dtj_new_stack_record(addr, rec, jc)); +} + +static jobject +dtj_new_tuple_stack_record(const dtrace_aggdata_t *data, + const dtrace_recdesc_t *rec, const char *s, dtj_java_consumer_t *jc) +{ + caddr_t addr; + JNIEnv *jenv = jc->dtjj_jenv; + + jobjectArray frames = NULL; + jobject jobj = NULL; /* tuple element */ + jstring jstr = NULL; + + /* Get raw stack data */ + addr = data->dtada_data + rec->dtrd_offset; + jobj = dtj_new_stack_record(addr, rec, jc); + if (!jobj) { + return (NULL); /* java exception pending */ + } + + jstr = dtj_NewStringNative(jenv, s); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jobj); + return (NULL); + } + frames = (*jenv)->CallStaticObjectMethod(jenv, g_stack_jc, + g_parsestack_jsm, jstr); + (*jenv)->DeleteLocalRef(jenv, jstr); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jobj); + return (NULL); + } + dtj_attach_frames(jc, jobj, frames); + (*jenv)->DeleteLocalRef(jenv, frames); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + + return (jobj); +} + +/* Caller must be holding per-consumer lock */ +static void +dtj_aggwalk_init(dtj_java_consumer_t *jc) +{ + jc->dtjj_consumer->dtjc_aggid = -1; + jc->dtjj_consumer->dtjc_expected = -1; + if (jc->dtjj_tuple != NULL) { + /* assert without crashing */ + dtj_throw_illegal_state(jc->dtjj_jenv, + "stale aggregation tuple"); + } +} + +static jobject +dtj_new_stack_record(caddr_t addr, const dtrace_recdesc_t *rec, + dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + dtrace_actkind_t act; + uint64_t *pc; + pid_t pid = -1; + int size; /* size of raw bytes not including trailing zeros */ + int i; /* index of last non-zero byte */ + + jbyteArray raw = NULL; + jobject stack = NULL; /* return value */ + + /* trim trailing zeros */ + for (i = rec->dtrd_size - 1; (i >= 0) && !addr[i]; --i) { + } + size = (i + 1); + raw = (*jenv)->NewByteArray(jenv, size); + if (!raw) { + return (NULL); /* OutOfMemoryError pending */ + } + (*jenv)->SetByteArrayRegion(jenv, raw, 0, size, + (const jbyte *)addr); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, raw); + return (NULL); + } + + /* Create StackValueRecord instance from raw stack data */ + act = rec->dtrd_action; + switch (act) { + case DTRACEACT_STACK: + stack = (*jenv)->NewObject(jenv, g_stack_jc, + g_stackinit_jm, raw); + break; + case DTRACEACT_USTACK: + case DTRACEACT_JSTACK: + /* Get pid of user process */ + pc = (uint64_t *)(uintptr_t)addr; + pid = (pid_t)*pc; + stack = (*jenv)->NewObject(jenv, g_ustack_jc, + g_ustackinit_jm, pid, raw); + break; + default: + dtj_throw_illegal_argument(jenv, + "Expected stack action, got %d\n", act); + } + (*jenv)->DeleteLocalRef(jenv, raw); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (NULL); + } + return (stack); +} + +/* + * Return NULL if java exception pending, otherwise return Distribution value. + */ +static jobject +dtj_new_distribution(const dtrace_aggdata_t *data, const dtrace_recdesc_t *rec, + dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jlongArray jbuckets = NULL; + jobject jdist = NULL; /* return value */ + + dtrace_actkind_t act = rec->dtrd_action; + /* LINTED - alignment */ + int64_t *aggbuckets = (int64_t *) + (data->dtada_data + rec->dtrd_offset); + size_t size = rec->dtrd_size; + int64_t value; + uint64_t normal = data->dtada_normal; + int64_t base, step; + int levels; + int n; /* number of buckets */ + + /* distribution */ + if (act == DTRACEAGG_LQUANTIZE) { + /* first "bucket" used for range and step */ + value = *aggbuckets++; + base = DTRACE_LQUANTIZE_BASE(value); + step = DTRACE_LQUANTIZE_STEP(value); + levels = DTRACE_LQUANTIZE_LEVELS(value); + size -= sizeof (int64_t); /* exclude non-bucket */ + /* + * Add one for the base bucket and one for the bucket of values + * less than the base. + */ + n = levels + 2; + } else { + n = DTRACE_QUANTIZE_NBUCKETS; + levels = n - 1; /* levels excludes base */ + } + if (size != (n * sizeof (uint64_t)) || n < 1) { + dtj_throw_illegal_state(jenv, + "size mismatch: record %d, buckets %d", size, + (n * sizeof (uint64_t))); + WRAP_EXCEPTION(jenv); + return (NULL); + } + + jbuckets = (*jenv)->NewLongArray(jenv, n); + if (!jbuckets) { + return (NULL); /* exception pending */ + } + if (n > 0) { + (*jenv)->SetLongArrayRegion(jenv, jbuckets, 0, n, aggbuckets); + /* check for ArrayIndexOutOfBounds */ + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jbuckets); + return (NULL); + } + } + + if (act == DTRACEAGG_LQUANTIZE) { + /* Must pass 64-bit base and step or constructor gets junk. */ + jdist = (*jenv)->NewObject(jenv, g_ldist_jc, g_ldistinit_jm, + base, step, jbuckets); + } else { + jdist = (*jenv)->NewObject(jenv, g_dist_jc, g_distinit_jm, + jbuckets); + } + + (*jenv)->DeleteLocalRef(jenv, jbuckets); + if (!jdist) { + return (NULL); /* exception pending */ + } + + if (normal != 1) { + (*jenv)->CallVoidMethod(jenv, jdist, g_dist_normal_jm, normal); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->DeleteLocalRef(jenv, jdist); + return (NULL); + } + } + return (jdist); +} + +static void +dtj_attach_frames(dtj_java_consumer_t *jc, jobject stack, + jobjectArray frames) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + if ((*jenv)->IsInstanceOf(jenv, stack, g_stack_jc)) { + (*jenv)->CallVoidMethod(jenv, stack, g_stackset_frames_jm, + frames); + } else if ((*jenv)->IsInstanceOf(jenv, stack, g_ustack_jc)) { + (*jenv)->CallVoidMethod(jenv, stack, g_ustackset_frames_jm, + frames); + } +} + +/* + * Note: It is not valid to look outside the current libdtrace record in the + * given aggdata (except to get the aggregation ID from the first record). + * + * Return DTRACE_HANDLE_ABORT if java exception pending, otherwise + * DTRACE_HANDLE_OK. + */ +static int +dtj_agghandler(const dtrace_bufdata_t *bufdata, dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + const dtrace_aggdata_t *aggdata = bufdata->dtbda_aggdata; + const dtrace_aggdesc_t *aggdesc; + const dtrace_recdesc_t *rec = bufdata->dtbda_recdesc; + const char *s = bufdata->dtbda_buffered; + dtrace_actkind_t act = DTRACEACT_NONE; + int64_t aggid; + + jobject jobj = NULL; + + if (aggdata == NULL) { + /* Assert without crashing */ + dtj_throw_illegal_state(jenv, "null aggdata"); + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + aggdesc = aggdata->dtada_desc; + + /* + * Get the aggregation ID from the first record. + */ + /* LINTED - alignment */ + aggid = *((int64_t *)(aggdata->dtada_data + + aggdesc->dtagd_rec[0].dtrd_offset)); + if (aggid < 0) { + /* Assert without crashing */ + dtj_throw_illegal_argument(jenv, "negative aggregation ID"); + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + + if (jc->dtjj_consumer->dtjc_printa_snaptime) { + /* Append buffered output if this is a printa() callback. */ + jstring jstr = dtj_NewStringNative(jenv, s); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + /* + * StringBuffer append() returns a reference to the + * StringBuffer; must not leak the returned reference. + */ + jobj = (*jenv)->CallObjectMethod(jenv, + jc->dtjj_printa_buffer, g_buf_append_str_jm, jstr); + (*jenv)->DeleteLocalRef(jenv, jstr); + (*jenv)->DeleteLocalRef(jenv, jobj); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + } else { + /* + * Test whether to include the aggregation if this is a + * getAggregate() callback. Optimization: perform the inclusion + * test only when the aggregation has changed. + */ + if (aggid != jc->dtjj_consumer->dtjc_aggid) { + jc->dtjj_consumer->dtjc_included = + dtj_is_included(aggdata, jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + } + if (!jc->dtjj_consumer->dtjc_included) { + return (DTRACE_HANDLE_OK); + } + } + jc->dtjj_consumer->dtjc_aggid = aggid; + + /* + * Determine the expected number of tuple members. While it is not + * technically valid to look outside the current record in the current + * aggdata, this implementation does so without a known failure case. + * Any method relying only on the current callback record makes riskier + * assumptions and still does not cover every corner case (for example, + * counting the records from index 1 up to and not including the index + * of the current DTRACE_BUFDATA_AGGVAL record, which fails when a + * format string specifies the value ahead of one or more tuple + * elements). Knowing that the calculation of the expected tuple size + * is technically invalid (because it looks outside the current record), + * we make the calculation at the earliest opportunity, before anything + * might happen to invalidate any part of the aggdata. It ought to be + * safe in any case: dtrd_action and dtrd_size do not appear ever to be + * overwritten, and dtrd_offset is not used outside the current record. + * + * It is possible (if the assumptions here ever prove untrue) that the + * libdtrace buffered output handler may need to be enhanced to provide + * the expected number of tuple members. + */ + if (jc->dtjj_consumer->dtjc_expected < 0) { + int r; + for (r = 1; r < aggdesc->dtagd_nrecs; ++r) { + act = aggdesc->dtagd_rec[r].dtrd_action; + if (DTRACEACT_ISAGG(act) || + aggdesc->dtagd_rec[r].dtrd_size == 0) { + break; + } + } + jc->dtjj_consumer->dtjc_expected = r - 1; + } + + if (bufdata->dtbda_flags & DTRACE_BUFDATA_AGGKEY) { + /* record value is a tuple member */ + + if (jc->dtjj_tuple == NULL) { + jc->dtjj_tuple = (*jenv)->NewObject(jenv, + g_tuple_jc, g_tupleinit_jm); + if (!jc->dtjj_tuple) { + /* java exception pending */ + return (DTRACE_HANDLE_ABORT); + } + } + + act = rec->dtrd_action; + + switch (act) { + case DTRACEACT_STACK: + case DTRACEACT_USTACK: + case DTRACEACT_JSTACK: + jobj = dtj_new_tuple_stack_record(aggdata, rec, s, jc); + break; + default: + jobj = dtj_recdata(jc, rec->dtrd_size, + (aggdata->dtada_data + rec->dtrd_offset)); + } + + if (!jobj) { + /* java exception pending */ + return (DTRACE_HANDLE_ABORT); + } + + (*jenv)->CallVoidMethod(jenv, jc->dtjj_tuple, + g_tupleadd_jm, jobj); + (*jenv)->DeleteLocalRef(jenv, jobj); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + } else if (bufdata->dtbda_flags & DTRACE_BUFDATA_AGGVAL) { + /* + * Record value is that of an aggregating action. The printa() + * format string may place the tuple ahead of the aggregation + * value(s), so we can't be sure we have the tuple until we get + * the AGGLAST flag indicating the last callback associated with + * the current tuple. Save the aggregation value or values + * (multiple values if more than one aggregation is passed to + * printa()) until then. + */ + dtj_aggval_t *aggval; + + jstring jvalue = NULL; + + jvalue = dtj_new_aggval(jc, aggdata, rec); + if (!jvalue) { + /* java exception pending */ + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + aggval = dtj_aggval_create(jenv, jvalue, aggdesc->dtagd_name, + aggid); + if (!aggval) { + /* OutOfMemoryError pending */ + (*jenv)->DeleteLocalRef(jenv, jvalue); + return (DTRACE_HANDLE_ABORT); + } + if (!dtj_list_add(jc->dtjj_aggval_list, aggval)) { + /* deletes jvalue reference */ + dtj_aggval_destroy(aggval, jenv); + dtj_throw_out_of_memory(jenv, "Failed to add aggval"); + return (DTRACE_HANDLE_ABORT); + } + } + + if (bufdata->dtbda_flags & DTRACE_BUFDATA_AGGLAST) { + /* No more values associated with the current tuple. */ + + dtj_aggval_t *aggval; + uu_list_walk_t *itr; + int tuple_member_count; + + jobject jrec = NULL; + jstring jname = NULL; + + if (jc->dtjj_consumer->dtjc_expected == 0) { + /* + * singleton aggregation declared in D with no square + * brackets + */ + jc->dtjj_tuple = (*jenv)->GetStaticObjectField(jenv, + g_tuple_jc, g_tuple_EMPTY_jsf); + if (jc->dtjj_tuple == NULL) { + dtj_throw_out_of_memory(jenv, + "Failed to reference Tuple.EMPTY"); + return (DTRACE_HANDLE_ABORT); + } + } + + if (jc->dtjj_tuple == NULL) { + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdatainvalidate_printa_jm); + } + + tuple_member_count = (*jenv)->CallIntMethod(jenv, + jc->dtjj_tuple, g_tuplesize_jm); + if (tuple_member_count < + jc->dtjj_consumer->dtjc_expected) { + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdatainvalidate_printa_jm); + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_tuple); + jc->dtjj_tuple = NULL; + } + + if (jc->dtjj_tuple == NULL) { + goto printa_output; + } + + itr = uu_list_walk_start(jc->dtjj_aggval_list, 0); + while ((aggval = uu_list_walk_next(itr)) != NULL) { + /* + * new AggregationRecord: Combine the aggregation value + * with the saved tuple and add it to the current + * Aggregate or PrintaRecord. + */ + jrec = (*jenv)->NewObject(jenv, g_aggrec_jc, + g_aggrecinit_jm, jc->dtjj_tuple, + aggval->dtja_value); + (*jenv)->DeleteLocalRef(jenv, aggval->dtja_value); + aggval->dtja_value = NULL; + if (!jrec) { + /* java exception pending */ + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + + /* aggregation name */ + jname = (*jenv)->NewStringUTF(jenv, + aggval->dtja_aggname); + if (!jname) { + /* OutOfMemoryError pending */ + (*jenv)->DeleteLocalRef(jenv, jrec); + return (DTRACE_HANDLE_ABORT); + } + + /* + * If the printa() format string specifies the value of + * the aggregating action multiple times, PrintaRecord + * ignores the attempt to add the duplicate record. + */ + if (jc->dtjj_consumer->dtjc_printa_snaptime) { + /* add to PrintaRecord */ + (*jenv)->CallVoidMethod(jenv, + jc->dtjj_probedata, + g_pdataadd_aggrec_jm, + jname, aggval->dtja_aggid, jrec); + } else { + /* add to Aggregate */ + (*jenv)->CallVoidMethod(jenv, + jc->dtjj_aggregate, g_aggaddrec_jm, + jname, aggval->dtja_aggid, jrec); + } + + (*jenv)->DeleteLocalRef(jenv, jrec); + (*jenv)->DeleteLocalRef(jenv, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + } + uu_list_walk_end(itr); + dtj_list_clear(jc->dtjj_aggval_list, dtj_aggval_destroy, + jenv); + +printa_output: + if (jc->dtjj_consumer->dtjc_printa_snaptime) { + /* + * Get the formatted string associated with the current + * tuple if this is a printa() callback. + */ + jstring jstr = (*jenv)->CallObjectMethod(jenv, + jc->dtjj_printa_buffer, g_tostring_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + /* + * Clear the StringBuffer: this does not throw + * exceptions. Reuse the StringBuffer until the end of + * the current probedata then dispose of it. + */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_printa_buffer, + g_bufsetlen_jm, 0); + /* Add formatted string to PrintaRecord */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_probedata, + g_pdataadd_printa_str_jm, jc->dtjj_tuple, jstr); + (*jenv)->DeleteLocalRef(jenv, jstr); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTRACE_HANDLE_ABORT); + } + } + + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_tuple); + jc->dtjj_tuple = NULL; + jc->dtjj_consumer->dtjc_expected = -1; + } + + return (DTRACE_HANDLE_OK); +} + +/* + * Return B_TRUE if the aggregation is included, B_FALSE otherwise. Only in the + * latter case might there be an exception pending. + */ +static boolean_t +dtj_is_included(const dtrace_aggdata_t *data, dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + if (jc->dtjj_aggregate_spec) { + jboolean included; + jstring aggname = NULL; + + const dtrace_aggdesc_t *aggdesc = data->dtada_desc; + aggname = (*jenv)->NewStringUTF(jenv, aggdesc->dtagd_name); + if (!aggname) { + /* java exception pending */ + return (B_FALSE); + } + + included = (*jenv)->CallBooleanMethod(jenv, + jc->dtjj_aggregate_spec, g_aggspec_included_jm, + aggname); + (*jenv)->DeleteLocalRef(jenv, aggname); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (B_FALSE); + } + + return (included); + } + + return (B_TRUE); +} + +/* + * Return NULL if a java exception is pending, otherwise return a new + * AggregationValue instance. + */ +static jobject +dtj_new_aggval(dtj_java_consumer_t *jc, const dtrace_aggdata_t *data, + const dtrace_recdesc_t *rec) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jobject jvalue = NULL; /* return value */ + + dtrace_actkind_t act; + uint64_t normal; + caddr_t addr; + int64_t value; + + act = rec->dtrd_action; + normal = data->dtada_normal; + addr = data->dtada_data + rec->dtrd_offset; + if (act == DTRACEAGG_AVG) { + value = dtj_average(addr, normal); + } else { + /* LINTED - alignment */ + value = (*((int64_t *)addr)) / normal; + } + + if (act == DTRACEAGG_QUANTIZE || act == DTRACEAGG_LQUANTIZE) { + jvalue = dtj_new_distribution(data, rec, jc); + } else { + switch (act) { + case DTRACEAGG_COUNT: + jvalue = (*jenv)->NewObject(jenv, g_aggcount_jc, + g_aggcountinit_jm, value); + break; + case DTRACEAGG_SUM: + jvalue = (*jenv)->NewObject(jenv, g_aggsum_jc, + g_aggsuminit_jm, value); + break; + case DTRACEAGG_AVG: + jvalue = (*jenv)->NewObject(jenv, g_aggavg_jc, + g_aggavginit_jm, value, dtj_avg_total(addr, + normal), dtj_avg_count(addr)); + break; + case DTRACEAGG_MIN: + jvalue = (*jenv)->NewObject(jenv, g_aggmin_jc, + g_aggmininit_jm, value); + break; + case DTRACEAGG_MAX: + jvalue = (*jenv)->NewObject(jenv, g_aggmax_jc, + g_aggmaxinit_jm, value); + break; + default: + jvalue = NULL; + dtj_throw_illegal_argument(jenv, + "unexpected aggregation action: %d", act); + } + } + + return (jvalue); +} + +/* + * Stops the given consumer if it is running. Throws DTraceException if + * dtrace_stop() fails and no other exception is already pending. Clears and + * rethrows any pending exception in order to grab the global lock safely. + */ +void +dtj_stop(dtj_java_consumer_t *jc) +{ + JNIEnv *jenv; + int rc; + jthrowable e; + + switch (jc->dtjj_consumer->dtjc_state) { + case DTJ_CONSUMER_GO: + case DTJ_CONSUMER_START: + break; + default: + return; + } + + jenv = jc->dtjj_jenv; + e = (*jenv)->ExceptionOccurred(jenv); + if (e) { + (*jenv)->ExceptionClear(jenv); + } + + (*jenv)->MonitorEnter(jenv, g_caller_jc); + if ((*jenv)->ExceptionCheck(jenv)) { + goto rethrow; + } + + rc = dtrace_status(jc->dtjj_consumer->dtjc_dtp); + if (rc != DTRACE_STATUS_STOPPED) { + rc = dtrace_stop(jc->dtjj_consumer->dtjc_dtp); + } + + (*jenv)->MonitorExit(jenv, g_caller_jc); + if ((*jenv)->ExceptionCheck(jenv)) { + goto rethrow; + } + + if (rc == -1) { + (*jenv)->MonitorEnter(jenv, g_caller_jc); + if ((*jenv)->ExceptionCheck(jenv)) { + goto rethrow; + } + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, + "couldn't stop tracing: %s", + dtrace_errmsg(jc->dtjj_consumer->dtjc_dtp, + dtrace_errno(jc->dtjj_consumer->dtjc_dtp))); + /* safe to call with pending exception */ + (*jenv)->MonitorExit(jenv, g_caller_jc); + } else { + jc->dtjj_consumer->dtjc_state = DTJ_CONSUMER_STOP; + } + +rethrow: + if (e) { + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Favor earlier pending exception over + * exception thrown in this function. + */ + (*jenv)->ExceptionClear(jenv); + } + (*jenv)->Throw(jenv, e); + (*jenv)->DeleteLocalRef(jenv, e); + } +} + +/* + * Return Aggregate instance, or null if java exception pending. + */ +jobject +dtj_get_aggregate(dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + hrtime_t snaptime; + int rc; + + jobject aggregate = NULL; + + /* + * Aggregations must be snapped, walked, and cleared atomically, + * otherwise clearing loses data accumulated since the most recent snap. + * This per-consumer lock prevents dtrace_work() from snapping or + * clearing aggregations while we're in the middle of this atomic + * operation, so we continue to hold it until done clearing. + */ + (*jenv)->MonitorEnter(jenv, jc->dtjj_consumer_lock); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (NULL); + } + + dtj_aggwalk_init(jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + + /* + * Snap aggregations + * + * We need to record the snaptime here for the caller. Leaving it to + * the caller to record the snaptime before calling getAggregate() may + * be inaccurate because of the indeterminate delay waiting on the + * consumer lock before calling dtrace_aggregate_snap(). + */ + snaptime = gethrtime(); + if (dtrace_aggregate_snap(jc->dtjj_consumer->dtjc_dtp) != 0) { + dtj_error_t e; + if (dtj_get_dtrace_error(jc, &e) == DTJ_OK) { + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, e.dtje_message); + } + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + + /* Create the Java representation of the aggregate snapshot. */ + aggregate = (*jenv)->NewObject(jenv, g_agg_jc, g_agginit_jm, + snaptime); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + jc->dtjj_aggregate = aggregate; + + /* + * Walk the aggregate, converting the data into Java Objects. Traverse + * in order by aggregation ID first and tuple second by using + * dtrace_aggregate_walk_keysorted (uses varkeycmp). We cannot do the + * same for aggregations generated by the printa() action, since + * dtrace_work() traverses aggregation data in the order determined by + * the various "aggsort" options. Functions used by both the consumer + * loop and the competing getAggregate() thread must not depend on the + * ordering of records by tuple key. + * + * It is impractical to hold the global lock around + * dtrace_aggregate_print(), since it may take a long time (e.g. an + * entire second) if it performs expensive conversions such as that + * needed for user stack traces. Most libdtrace functions are not + * guaranteed to be MT-safe, even when each thread has its own dtrace + * handle; or even if they are safe, there is no guarantee that future + * changes may not make them unsafe. Fortunately in this case, however, + * only a per-consumer lock is necessary to avoid conflict with + * dtrace_work() running in another thread (the consumer loop). + */ + rc = dtrace_aggregate_print(jc->dtjj_consumer->dtjc_dtp, NULL, + dtrace_aggregate_walk_keysorted); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + if (rc != 0) { + dtj_error_t e; + if (dtj_get_dtrace_error(jc, &e) != DTJ_OK) { + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + + if (e.dtje_number != EINTR) { + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, e.dtje_message); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + } + + dtj_aggwalk_init(jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + + /* + * dtrace_aggregate_clear() clears all aggregations, and we need to + * clear aggregations selectively. It also fails to preserve the + * lquantize() range and step size; using aggregate_walk() to clear + * aggregations does not have this problem. + */ + rc = dtrace_aggregate_walk(jc->dtjj_consumer->dtjc_dtp, dtj_clear, jc); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + if (rc != 0) { + dtj_error_t e; + if (dtj_get_dtrace_error(jc, &e) == DTJ_OK) { + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, e.dtje_message); + } + /* release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (NULL); + } + + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (NULL); + } + + aggregate = jc->dtjj_aggregate; + jc->dtjj_aggregate = NULL; + + return (aggregate); +} + +/* + * Process any requests, such as the setting of runtime options, enqueued during + * dtrace_sleep(). A Java exception is pending if this function returns + * DTJ_ERR. + */ +static dtj_status_t +dtj_process_requests(dtj_java_consumer_t *jc) +{ + dtj_request_t *r; + uu_list_t *list = jc->dtjj_consumer->dtjc_request_list; + pthread_mutex_t *list_lock = &jc->dtjj_consumer-> + dtjc_request_list_lock; + const char *opt; + const char *val; + + (void) pthread_mutex_lock(list_lock); + while (!dtj_list_empty(list)) { + r = uu_list_first(list); + uu_list_remove(list, r); + + switch (r->dtjr_type) { + case DTJ_REQUEST_OPTION: + opt = dtj_string_list_first(r->dtjr_args); + val = dtj_string_list_last(r->dtjr_args); + if (dtrace_setopt(jc->dtjj_consumer->dtjc_dtp, opt, + val) == -1) { + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, + "failed to set %s: %s", opt, + dtrace_errmsg(jc->dtjj_consumer->dtjc_dtp, + dtrace_errno(jc->dtjj_consumer->dtjc_dtp))); + dtj_request_destroy(r, NULL); + (void) pthread_mutex_unlock(list_lock); + return (DTJ_ERR); + } + break; + } + dtj_request_destroy(r, NULL); + } + (void) pthread_mutex_unlock(list_lock); + return (DTJ_OK); +} + +/* + * Return DTJ_OK if the consumer loop is stopped normally by either the exit() + * action or the Consumer stop() method. Otherwise return DTJ_ERR if the + * consumer loop terminates abnormally with an exception pending. + */ +dtj_status_t +dtj_consume(dtj_java_consumer_t *jc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + dtrace_hdl_t *dtp = jc->dtjj_consumer->dtjc_dtp; + boolean_t done = B_FALSE; + dtj_error_t e; + + do { + if (!jc->dtjj_consumer->dtjc_interrupt) { + dtrace_sleep(dtp); + } + + if (jc->dtjj_consumer->dtjc_interrupt) { + done = B_TRUE; + dtj_stop(jc); + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Exception left pending by Consumer + * getAggregate() method. + */ + return (DTJ_ERR); + } + } else if (jc->dtjj_consumer->dtjc_process_list != NULL) { + int nprocs = uu_list_numnodes(jc->dtjj_consumer-> + dtjc_process_list); + if (jc->dtjj_consumer->dtjc_procs_ended == nprocs) { + done = B_TRUE; + dtj_stop(jc); + } + } + + /* + * Functions like dtrace_setopt() are not safe to call during + * dtrace_sleep(). Check the request list every time we wake up + * from dtrace_sleep(). + */ + if (!done) { + if (dtj_process_requests(jc) != DTJ_OK) { + /* Do not wrap DTraceException */ + return (DTJ_ERR); + } + } + + /* + * Use the per-consumer lock to avoid conflict with + * get_aggregate() called from another thread. + */ + (*jenv)->MonitorEnter(jenv, jc->dtjj_consumer_lock); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTJ_ERR); + } + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, + g_interval_began_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (DTJ_ERR); + } + jc->dtjj_consumer->dtjc_printa_snaptime = gethrtime(); + switch (dtrace_work(dtp, NULL, dtj_chew, dtj_chewrec, jc)) { + case DTRACE_WORKSTATUS_DONE: + done = B_TRUE; + break; + case DTRACE_WORKSTATUS_OKAY: + break; + default: + /* + * Check for a pending exception that got us to this + * error workstatus case. + */ + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Ensure valid initial state before releasing + * the consumer lock + */ + jc->dtjj_consumer->dtjc_printa_snaptime = 0; + /* Do not wrap DTraceException */ + /* Release per-consumer lock */ + (*jenv)->MonitorExit(jenv, + jc->dtjj_consumer_lock); + return (DTJ_ERR); + } + + if (dtj_get_dtrace_error(jc, &e) != DTJ_OK) { + /* java exception pending */ + jc->dtjj_consumer->dtjc_printa_snaptime = 0; + /* Release per-consumer lock */ + (*jenv)->MonitorExit(jenv, + jc->dtjj_consumer_lock); + return (DTJ_ERR); + } + + if (e.dtje_number != EINTR) { + /* Do not wrap DTraceException */ + dtj_throw_dtrace_exception(jc, e.dtje_message); + jc->dtjj_consumer->dtjc_printa_snaptime = 0; + /* Release per-consumer lock */ + (*jenv)->MonitorExit(jenv, + jc->dtjj_consumer_lock); + return (DTJ_ERR); + } + } + /* + * Check for ConsumerException before doing anything else with + * the JNIEnv. + */ + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Do not wrap exception thrown from ConsumerListener. + */ + jc->dtjj_consumer->dtjc_printa_snaptime = 0; + /* Release per-consumer lock */ + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + return (DTJ_ERR); + } + jc->dtjj_consumer->dtjc_printa_snaptime = 0; + /* + * Notify ConsumerListeners the the dtrace_work() interval ended + * before releasing the lock. + */ + (*jenv)->CallVoidMethod(jenv, jc->dtjj_caller, + g_interval_ended_jm); + (*jenv)->MonitorExit(jenv, jc->dtjj_consumer_lock); + if ((*jenv)->ExceptionCheck(jenv)) { + WRAP_EXCEPTION(jenv); + return (DTJ_ERR); + } + + /* + * Check for a temporarily cleared exception set by a handler + * that could not safely leave the exception pending because it + * could not return an abort signal. Rethrow it now that it's + * safe to do so (when it's possible to ensure that no JNI calls + * will be made that are unsafe while an exception is pending). + */ + if (jc->dtjj_exception) { + (*jenv)->Throw(jenv, jc->dtjj_exception); + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_exception); + jc->dtjj_exception = NULL; + return (DTJ_ERR); + } + } while (!done); + + return (DTJ_OK); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_error.c b/usr/src/lib/libdtrace_jni/common/dtj_error.c new file mode 100644 index 0000000000..7806fe90d4 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_error.c @@ -0,0 +1,79 @@ +/* + * 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 + +/* + * Not currently providing name equivalents of dtrace_errno, only the error + * message provided by libdtrace (which cannot be localized). The reason is + * that the EDT_ enumeration is still private. The DTRACEFLT_ values are + * public, however, so the API provides string equivalents for runtime faults + * encountered in the error handler (see dtrace_handle_err()). The API provides + * the error as a string rather than an integer so that user applications do not + * break if the integer values change. + */ + +const char * +dtj_get_fault_name(int fault) +{ + const char *name = NULL; + + switch (fault) { + case DTRACEFLT_BADADDR: + name = "DTRACEFLT_BADADDR"; + break; + case DTRACEFLT_BADALIGN: + name = "DTRACEFLT_BADALIGN"; + break; + case DTRACEFLT_ILLOP: + name = "DTRACEFLT_ILLOP"; + break; + case DTRACEFLT_DIVZERO: + name = "DTRACEFLT_DIVZERO"; + break; + case DTRACEFLT_NOSCRATCH: + name = "DTRACEFLT_NOSCRATCH"; + break; + case DTRACEFLT_KPRIV: + name = "DTRACEFLT_KPRIV"; + break; + case DTRACEFLT_UPRIV: + name = "DTRACEFLT_UPRIV"; + break; + case DTRACEFLT_TUPOFLOW: + name = "DTRACEFLT_TUPOFLOW"; + break; + case DTRACEFLT_LIBRARY: + name = "DTRACEFLT_LIBRARY"; + break; + default: + name = NULL; + } + + return (name); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_jnitab.c b/usr/src/lib/libdtrace_jni/common/dtj_jnitab.c new file mode 100644 index 0000000000..f0f4cc6c8e --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_jnitab.c @@ -0,0 +1,853 @@ +/* + * 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 +#include +#include +#include +#include +#include + +/* + * dtj_jnitab.c defines the JNI table of classes, methods, and fields belonging + * to the Java DTrace API. Another JNI table defining classes from the JDK is + * defined in dtj_util.c. Utility functions specific to the Java DTrace API are + * also defined here, while general utilities are defined in dtj_util.c. + */ + +static uu_list_pool_t *g_request_pool = NULL; +static uu_list_pool_t *g_program_pool = NULL; +static uu_list_pool_t *g_aggval_pool = NULL; + +static boolean_t dtj_check_request_pool(void); +static boolean_t dtj_check_program_pool(void); +static boolean_t dtj_check_aggval_pool(void); + +/* LocalConsumer */ +jclass g_caller_jc = 0; +jmethodID g_gethandle_jm = 0; +jmethodID g_sethandle_jm = 0; +jmethodID g_pdatanext_jm = 0; +jmethodID g_drop_jm = 0; +jmethodID g_error_jm = 0; +jmethodID g_proc_jm = 0; +jmethodID g_interval_began_jm = 0; +jmethodID g_interval_ended_jm = 0; +jfieldID g_consumer_lock_jf = 0; + +/* DTraceException */ +jclass g_dtx_jc = 0; +jmethodID g_dtxinit_jm = 0; + +/* InterfaceAttributes */ +jclass g_attr_jc = 0; +jmethodID g_attrinit_jm = 0; +jmethodID g_attrset_name_jm = 0; +jmethodID g_attrset_data_jm = 0; +jmethodID g_attrset_class_jm = 0; + +/* ProbeDescription */ +jclass g_probedesc_jc = 0; +jmethodID g_probedescinit_jm = 0; +jfieldID g_probedesc_id_jf = 0; + +/* ProbeInfo */ +jclass g_probeinfo_jc = 0; +jmethodID g_probeinfoinit_jm = 0; + +/* Probe */ +jclass g_probe_jc = 0; +jmethodID g_probeinit_jm = 0; + +/* Program */ +jclass g_program_jc = 0; +jmethodID g_proginit_jm = 0; +jfieldID g_progid_jf = 0; +jfieldID g_proginfo_jf = 0; + +/* Program.File */ +jclass g_programfile_jc = 0; +jmethodID g_fproginit_jm = 0; + +/* ProgramInfo */ +jclass g_proginfo_jc = 0; +jmethodID g_proginfoinit_jm = 0; + +/* Flow */ +jclass g_flow_jc = 0; +jmethodID g_flowinit_jm = 0; + +/* ProbeData */ +jclass g_pdata_jc = 0; +jmethodID g_pdatainit_jm = 0; +jmethodID g_pdataadd_jm = 0; +jmethodID g_pdataadd_rec_jm = 0; +jmethodID g_pdataadd_trace_jm = 0; +jmethodID g_pdataadd_stack_jm = 0; +jmethodID g_pdataadd_printf_jm = 0; +jmethodID g_pdataadd_printa_jm = 0; +jmethodID g_pdatainvalidate_printa_jm = 0; +jmethodID g_pdataadd_aggrec_jm = 0; +jmethodID g_pdataadd_printa_str_jm = 0; +jmethodID g_pdataadd_exit_jm = 0; +jmethodID g_pdataattach_jm = 0; +jmethodID g_pdataset_formatted_jm = 0; +jmethodID g_pdataclear_jm = 0; + +/* Drop */ +jclass g_drop_jc = 0; +jmethodID g_dropinit_jm = 0; + +/* Error */ +jclass g_error_jc = 0; +jmethodID g_errinit_jm = 0; + +/* ProcessState */ +jclass g_process_jc = 0; +jmethodID g_procinit_jm = 0; +jmethodID g_procexit_jm = 0; + +/* Aggregate */ +jclass g_agg_jc = 0; +jmethodID g_agginit_jm = 0; +jmethodID g_aggaddrec_jm = 0; + +/* AggregateSpec */ +jclass g_aggspec_jc = 0; +jmethodID g_aggspec_included_jm = 0; +jmethodID g_aggspec_cleared_jm = 0; + +/* Tuple */ +jclass g_tuple_jc = 0; +jmethodID g_tupleinit_jm = 0; +jmethodID g_tupleadd_jm = 0; +jmethodID g_tuplesize_jm = 0; +jfieldID g_tuple_EMPTY_jsf = 0; + +/* AggregationRecord */ +jclass g_aggrec_jc = 0; +jmethodID g_aggrecinit_jm = 0; +jmethodID g_aggrecget_tuple_jm = 0; + +/* SumValue */ +jclass g_aggsum_jc = 0; +jmethodID g_aggsuminit_jm = 0; + +/* CountValue */ +jclass g_aggcount_jc = 0; +jmethodID g_aggcountinit_jm = 0; + +/* AvgValue */ +jclass g_aggavg_jc = 0; +jmethodID g_aggavginit_jm = 0; + +/* MinValue */ +jclass g_aggmin_jc = 0; +jmethodID g_aggmininit_jm = 0; + +/* MaxValue */ +jclass g_aggmax_jc = 0; +jmethodID g_aggmaxinit_jm = 0; + +/* KernelStackRecord */ +jclass g_stack_jc = 0; +jmethodID g_parsestack_jsm = 0; +jmethodID g_stackinit_jm = 0; +jmethodID g_stackset_frames_jm = 0; + +/* UserStackRecord */ +jclass g_ustack_jc = 0; +jmethodID g_ustackinit_jm = 0; +jmethodID g_ustackset_frames_jm = 0; + +/* Distribution */ +jclass g_adist_jc = 0; +jmethodID g_dist_normal_jm = 0; + +/* LogDistribution */ +jclass g_dist_jc = 0; +jmethodID g_distinit_jm = 0; + +/* LinearDistribution */ +jclass g_ldist_jc = 0; +jmethodID g_ldistinit_jm = 0; + + +static dtj_status_t +dtj_table_load(JNIEnv *jenv) +{ + static const dtj_table_entry_t table[] = { + /* LocalConsumer */ + { JCLASS, &g_caller_jc, + "org/opensolaris/os/dtrace/LocalConsumer" }, + { JMETHOD, &g_gethandle_jm, "getHandle", "()I" }, + { JMETHOD, &g_sethandle_jm, "setHandle", "(I)V" }, + { JMETHOD, &g_pdatanext_jm, "nextProbeData", + "(Lorg/opensolaris/os/dtrace/ProbeData;)V" }, + { JMETHOD, &g_drop_jm, "dataDropped", + "(Lorg/opensolaris/os/dtrace/Drop;)V" }, + { JMETHOD, &g_error_jm, "errorEncountered", + "(Lorg/opensolaris/os/dtrace/Error;)V" }, + { JMETHOD, &g_proc_jm, "processStateChanged", + "(Lorg/opensolaris/os/dtrace/ProcessState;)V" }, + { JMETHOD, &g_interval_began_jm, "intervalBegan", "()V" }, + { JMETHOD, &g_interval_ended_jm, "intervalEnded", "()V" }, + { JFIELD, &g_consumer_lock_jf, "consumerLock", + "Ljava/lang/Object;" }, + + /* DTraceException */ + { JCLASS, &g_dtx_jc, + "org/opensolaris/os/dtrace/DTraceException" }, + { JMETHOD, &g_dtxinit_jm, CONSTRUCTOR, + "(Ljava/lang/String;)V" }, + + /* InterfaceAttributes */ + { JCLASS, &g_attr_jc, + "org/opensolaris/os/dtrace/InterfaceAttributes" }, + { JMETHOD, &g_attrinit_jm, CONSTRUCTOR, "()V" }, + { JMETHOD, &g_attrset_name_jm, "setNameStability", + "(Ljava/lang/String;)V" }, + { JMETHOD, &g_attrset_data_jm, "setDataStability", + "(Ljava/lang/String;)V" }, + { JMETHOD, &g_attrset_class_jm, "setDependencyClass", + "(Ljava/lang/String;)V" }, + + /* ProbeDescription */ + { JCLASS, &g_probedesc_jc, + "org/opensolaris/os/dtrace/ProbeDescription" }, + { JMETHOD, &g_probedescinit_jm, CONSTRUCTOR, + "(Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Ljava/lang/String;)V" }, + { JFIELD, &g_probedesc_id_jf, "id", "I" }, + + /* ProbeInfo */ + { JCLASS, &g_probeinfo_jc, + "org/opensolaris/os/dtrace/ProbeInfo" }, + { JMETHOD, &g_probeinfoinit_jm, CONSTRUCTOR, + "(Lorg/opensolaris/os/dtrace/InterfaceAttributes;" + "Lorg/opensolaris/os/dtrace/InterfaceAttributes;" + ")V" }, + + /* Probe */ + { JCLASS, &g_probe_jc, "org/opensolaris/os/dtrace/Probe" }, + { JMETHOD, &g_probeinit_jm, CONSTRUCTOR, + "(Lorg/opensolaris/os/dtrace/ProbeDescription;" + "Lorg/opensolaris/os/dtrace/ProbeInfo;)V" }, + + /* Program */ + { JCLASS, &g_program_jc, + "org/opensolaris/os/dtrace/Program" }, + { JMETHOD, &g_proginit_jm, CONSTRUCTOR, "()V" }, + { JFIELD, &g_progid_jf, "id", "I" }, + { JFIELD, &g_proginfo_jf, "info", + "Lorg/opensolaris/os/dtrace/ProgramInfo;" }, + + /* Program.File */ + { JCLASS, &g_programfile_jc, + "org/opensolaris/os/dtrace/Program$File" }, + { JMETHOD, &g_fproginit_jm, CONSTRUCTOR, "()V" }, + + /* ProgramInfo */ + { JCLASS, &g_proginfo_jc, + "org/opensolaris/os/dtrace/ProgramInfo" }, + { JMETHOD, &g_proginfoinit_jm, CONSTRUCTOR, + "(Lorg/opensolaris/os/dtrace/InterfaceAttributes;" + "Lorg/opensolaris/os/dtrace/InterfaceAttributes;" + "I)V" }, + + /* Flow */ + { JCLASS, &g_flow_jc, "org/opensolaris/os/dtrace/Flow" }, + { JMETHOD, &g_flowinit_jm, CONSTRUCTOR, + "(Ljava/lang/String;I)V" }, + + /* ProbeData */ + { JCLASS, &g_pdata_jc, + "org/opensolaris/os/dtrace/ProbeData" }, + { JMETHOD, &g_pdatainit_jm, CONSTRUCTOR, + "(IILorg/opensolaris/os/dtrace/ProbeDescription;" + "Lorg/opensolaris/os/dtrace/Flow;I)V" }, + { JMETHOD, &g_pdataadd_jm, "addDataElement", + "(Ljava/lang/Object;)V" }, + { JMETHOD, &g_pdataadd_rec_jm, "addRecord", + "(Lorg/opensolaris/os/dtrace/Record;)V" }, + { JMETHOD, &g_pdataadd_trace_jm, "addTraceRecord", "(I)V" }, + { JMETHOD, &g_pdataadd_stack_jm, "addStackRecord", + "(ILjava/lang/String;)V" }, + { JMETHOD, &g_pdataadd_printf_jm, "addPrintfRecord", "()V" }, + { JMETHOD, &g_pdataadd_printa_jm, "addPrintaRecord", "(JZ)V" }, + { JMETHOD, &g_pdatainvalidate_printa_jm, + "invalidatePrintaRecord", "()V" }, + { JMETHOD, &g_pdataadd_aggrec_jm, "addAggregationRecord", + "(Ljava/lang/String;J" + "Lorg/opensolaris/os/dtrace/AggregationRecord;)V" }, + { JMETHOD, &g_pdataadd_printa_str_jm, + "addPrintaFormattedString", + "(Lorg/opensolaris/os/dtrace/Tuple;" + "Ljava/lang/String;)V" }, + { JMETHOD, &g_pdataadd_exit_jm, "addExitRecord", "(I)V" }, + { JMETHOD, &g_pdataattach_jm, "attachRecordElements", + "(II)V" }, + { JMETHOD, &g_pdataset_formatted_jm, "setFormattedString", + "(Ljava/lang/String;)V" }, + { JMETHOD, &g_pdataclear_jm, "clearNativeElements", "()V" }, + + /* Drop */ + { JCLASS, &g_drop_jc, "org/opensolaris/os/dtrace/Drop" }, + { JMETHOD, &g_dropinit_jm, CONSTRUCTOR, + "(ILjava/lang/String;JJLjava/lang/String;)V" }, + + /* Error */ + { JCLASS, &g_error_jc, "org/opensolaris/os/dtrace/Error" }, + { JMETHOD, &g_errinit_jm, CONSTRUCTOR, + "(Lorg/opensolaris/os/dtrace/ProbeDescription;IIII" + "Ljava/lang/String;JLjava/lang/String;)V" }, + + /* ProcessState */ + { JCLASS, &g_process_jc, + "org/opensolaris/os/dtrace/ProcessState" }, + { JMETHOD, &g_procinit_jm, CONSTRUCTOR, + "(ILjava/lang/String;ILjava/lang/String;" + "Ljava/lang/Integer;Ljava/lang/String;)V" }, + { JMETHOD, &g_procexit_jm, "setExitStatus", "(I)V" }, + + /* Aggregate */ + { JCLASS, &g_agg_jc, "org/opensolaris/os/dtrace/Aggregate" }, + { JMETHOD, &g_agginit_jm, CONSTRUCTOR, "(J)V" }, + { JMETHOD, &g_aggaddrec_jm, "addRecord", + "(Ljava/lang/String;J" + "Lorg/opensolaris/os/dtrace/AggregationRecord;)V" }, + + /* AggregateSpec */ + { JCLASS, &g_aggspec_jc, + "org/opensolaris/os/dtrace/AggregateSpec" }, + { JMETHOD, &g_aggspec_included_jm, "isIncluded", + "(Ljava/lang/String;)Z" }, + { JMETHOD, &g_aggspec_cleared_jm, "isCleared", + "(Ljava/lang/String;)Z" }, + + /* Tuple */ + { JCLASS, &g_tuple_jc, "org/opensolaris/os/dtrace/Tuple" }, + { JMETHOD, &g_tupleinit_jm, CONSTRUCTOR, "()V" }, + { JMETHOD, &g_tupleadd_jm, "addElement", + "(Ljava/lang/Object;)V" }, + { JMETHOD, &g_tuplesize_jm, "size", "()I" }, + { JFIELD_STATIC, &g_tuple_EMPTY_jsf, "EMPTY", + "Lorg/opensolaris/os/dtrace/Tuple;" }, + + /* AggregationRecord */ + { JCLASS, &g_aggrec_jc, + "org/opensolaris/os/dtrace/AggregationRecord" }, + { JMETHOD, &g_aggrecinit_jm, CONSTRUCTOR, + "(Lorg/opensolaris/os/dtrace/Tuple;" + "Lorg/opensolaris/os/dtrace/AggregationValue;)V" }, + { JMETHOD, &g_aggrecget_tuple_jm, "getTuple", + "()Lorg/opensolaris/os/dtrace/Tuple;" }, + + /* SumValue */ + { JCLASS, &g_aggsum_jc, + "org/opensolaris/os/dtrace/SumValue" }, + { JMETHOD, &g_aggsuminit_jm, CONSTRUCTOR, "(J)V" }, + + /* CountValue */ + { JCLASS, &g_aggcount_jc, + "org/opensolaris/os/dtrace/CountValue" }, + { JMETHOD, &g_aggcountinit_jm, CONSTRUCTOR, "(J)V" }, + + /* AvgValue */ + { JCLASS, &g_aggavg_jc, + "org/opensolaris/os/dtrace/AvgValue" }, + { JMETHOD, &g_aggavginit_jm, CONSTRUCTOR, "(JJJ)V" }, + + /* MinValue */ + { JCLASS, &g_aggmin_jc, + "org/opensolaris/os/dtrace/MinValue" }, + { JMETHOD, &g_aggmininit_jm, CONSTRUCTOR, "(J)V" }, + + /* MaxValue */ + { JCLASS, &g_aggmax_jc, + "org/opensolaris/os/dtrace/MaxValue" }, + { JMETHOD, &g_aggmaxinit_jm, CONSTRUCTOR, "(J)V" }, + + /* KernelStackRecord */ + { JCLASS, &g_stack_jc, + "org/opensolaris/os/dtrace/KernelStackRecord" }, + { JMETHOD_STATIC, &g_parsestack_jsm, "parse", + "(Ljava/lang/String;)" + "[Lorg/opensolaris/os/dtrace/StackFrame;" }, + { JMETHOD, &g_stackinit_jm, CONSTRUCTOR, "([B)V" }, + { JMETHOD, &g_stackset_frames_jm, "setStackFrames", + "([Lorg/opensolaris/os/dtrace/StackFrame;)V" }, + + /* UserStackRecord */ + { JCLASS, &g_ustack_jc, + "org/opensolaris/os/dtrace/UserStackRecord" }, + { JMETHOD, &g_ustackinit_jm, CONSTRUCTOR, "(I[B)V" }, + { JMETHOD, &g_ustackset_frames_jm, "setStackFrames", + "([Lorg/opensolaris/os/dtrace/StackFrame;)V" }, + + /* Distribution */ + { JCLASS, &g_adist_jc, + "org/opensolaris/os/dtrace/Distribution" }, + { JMETHOD, &g_dist_normal_jm, "normalizeBuckets", "(J)V" }, + + /* LogDistribution */ + { JCLASS, &g_dist_jc, + "org/opensolaris/os/dtrace/LogDistribution" }, + { JMETHOD, &g_distinit_jm, CONSTRUCTOR, "([J)V" }, + + /* LinearDistribution */ + { JCLASS, &g_ldist_jc, + "org/opensolaris/os/dtrace/LinearDistribution" }, + { JMETHOD, &g_ldistinit_jm, CONSTRUCTOR, "(JJ[J)V" }, + + { DTJ_TYPE_END } + }; + + return (dtj_cache_jni_classes(jenv, table)); +} + +dtj_status_t +dtj_load(JNIEnv *jenv) +{ + if (dtj_load_common(jenv) != DTJ_OK) { + /* Java Error pending */ + return (DTJ_ERR); + } + + return (dtj_table_load(jenv)); +} + +static boolean_t +dtj_check_request_pool(void) +{ + if (!g_request_pool) { + g_request_pool = uu_list_pool_create("g_request_pool", + sizeof (dtj_request_t), + offsetof(dtj_request_t, dtjr_node), + dtj_pointer_list_entry_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!g_request_pool) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +dtj_request_t * +dtj_request_create(JNIEnv *jenv, dtj_request_type_t type, ...) +{ + dtj_request_t *r; + + if (!dtj_check_request_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate request pool"); + return (NULL); + } + + r = uu_zalloc(sizeof (dtj_request_t)); + if (r) { + uu_list_node_init(r, &r->dtjr_node, g_request_pool); + r->dtjr_type = type; + r->dtjr_args = dtj_string_list_create(); + if (r->dtjr_args) { + va_list ap; + const char *arg; + int i, len; + + va_start(ap, type); + switch (type) { + case DTJ_REQUEST_OPTION: + len = 2; + break; + default: + len = 0; + } + + for (i = 0; i < len; ++i) { + arg = va_arg(ap, char *); + if (!dtj_string_list_add(r->dtjr_args, arg)) { + dtj_throw_out_of_memory(jenv, + "Failed to add request arg"); + uu_list_node_fini(r, &r->dtjr_node, + g_request_pool); + dtj_request_destroy(r, NULL); + r = NULL; + } + } + va_end(ap); + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate request arglist"); + uu_list_node_fini(r, &r->dtjr_node, g_request_pool); + dtj_request_destroy(r, NULL); + r = NULL; + } + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate request"); + } + + return (r); +} + +static boolean_t +dtj_check_program_pool(void) +{ + if (!g_program_pool) { + g_program_pool = uu_list_pool_create("g_program_pool", + sizeof (dtj_program_t), + offsetof(dtj_program_t, dtjp_node), + dtj_pointer_list_entry_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!g_program_pool) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +dtj_program_t * +dtj_program_create(JNIEnv *jenv, dtj_program_type_t type, const char *name) +{ + dtj_program_t *p; + + if (!dtj_check_program_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate program pool"); + return (NULL); + } + + p = uu_zalloc(sizeof (dtj_program_t)); + if (p) { + char *program_name; + + uu_list_node_init(p, &p->dtjp_node, g_program_pool); + p->dtjp_type = type; + program_name = malloc((size_t) + (sizeof (char)) * (strlen(name) + 1)); + if (program_name) { + (void) strcpy(program_name, name); + p->dtjp_name = program_name; + p->dtjp_enabled = B_FALSE; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate program name"); + uu_list_node_fini(p, &p->dtjp_node, g_program_pool); + dtj_program_destroy(p, NULL); + p = NULL; + } + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate program"); + } + + return (p); +} + +static boolean_t +dtj_check_aggval_pool(void) +{ + if (!g_aggval_pool) { + g_aggval_pool = uu_list_pool_create("g_aggval_pool", + sizeof (dtj_aggval_t), + offsetof(dtj_aggval_t, dtja_node), + dtj_pointer_list_entry_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!g_aggval_pool) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +dtj_aggval_t * +dtj_aggval_create(JNIEnv *jenv, jobject aggval, const char *aggname, + int64_t aggid) +{ + dtj_aggval_t *e; + + if (!dtj_check_aggval_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate aggval entry pool"); + return (NULL); + } + + e = uu_zalloc(sizeof (dtj_aggval_t)); + if (e) { + char *a_name; + + uu_list_node_init(e, &e->dtja_node, g_aggval_pool); + e->dtja_value = aggval; + a_name = malloc((size_t) + (sizeof (char)) * (strlen(aggname) + 1)); + if (a_name) { + (void) strcpy(a_name, aggname); + e->dtja_aggname = a_name; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate aggregation name"); + uu_list_node_fini(e, &e->dtja_node, g_aggval_pool); + /* caller responsible for input java reference */ + e->dtja_value = NULL; + dtj_aggval_destroy(e, jenv); + e = NULL; + } + e->dtja_aggid = aggid; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate aggval entry"); + } + + return (e); +} + +dtj_status_t +dtj_java_consumer_init(JNIEnv *jenv, dtj_java_consumer_t *jc) +{ + if (!dtj_check_aggval_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate aggval pool"); + return (DTJ_ERR); + } + + jc->dtjj_aggval_list = uu_list_create(g_aggval_pool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!jc->dtjj_aggval_list) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate aggval list"); + return (DTJ_ERR); + } + + /* Does not throw exceptions */ + jc->dtjj_consumer_lock = (*jenv)->GetObjectField(jenv, jc->dtjj_caller, + g_consumer_lock_jf); + + return (DTJ_OK); +} + +void +dtj_java_consumer_fini(JNIEnv *jenv, dtj_java_consumer_t *jc) +{ + if (jc) { + if (jc->dtjj_probedata) { + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_probedata); + jc->dtjj_probedata = NULL; + } + if (jc->dtjj_printa_buffer) { + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_printa_buffer); + jc->dtjj_printa_buffer = NULL; + } + if (jc->dtjj_aggregate) { + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_aggregate); + jc->dtjj_aggregate = NULL; + } + if (jc->dtjj_tuple) { + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_tuple); + jc->dtjj_tuple = NULL; + } + if (jc->dtjj_aggval_list) { + dtj_list_destroy(jc->dtjj_aggval_list, + dtj_aggval_destroy, jenv); + jc->dtjj_aggval_list = NULL; + } + + /* + * aggregate_spec records an input argument to a native JNI + * function (a reference we did not create), so we are not + * responsible for it. + */ + jc->dtjj_aggregate_spec = NULL; + + /* + * probelist records an in-out argument to a native JNI function + * (a reference we did not create), so we are not responsible + * for it. + */ + jc->dtjj_probelist = NULL; + + if (jc->dtjj_exception) { + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_exception); + jc->dtjj_exception = NULL; + } + (*jenv)->DeleteLocalRef(jenv, jc->dtjj_consumer_lock); + jc->dtjj_consumer_lock = NULL; + } +} + +dtj_consumer_t * +dtj_consumer_create(JNIEnv *jenv) +{ + dtj_consumer_t *c; + + if (!dtj_check_request_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate request pool"); + return (NULL); + } + + if (!dtj_check_program_pool()) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate program pool"); + return (NULL); + } + + c = uu_zalloc(sizeof (dtj_consumer_t)); + if (c) { + c->dtjc_request_list = uu_list_create(g_request_pool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!c->dtjc_request_list) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate consumer request list"); + dtj_consumer_destroy(c); + return (NULL); + } + (void) pthread_mutex_init(&c->dtjc_request_list_lock, NULL); + + c->dtjc_program_list = uu_list_create(g_program_pool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!c->dtjc_program_list) { + dtj_throw_out_of_memory(jenv, + "Failed to allocate consumer program list"); + dtj_consumer_destroy(c); + return (NULL); + } + + c->dtjc_probedata_rec_i = 0; + c->dtjc_probedata_act = DTRACEACT_NONE; + c->dtjc_aggid = -1; + c->dtjc_expected = -1; + c->dtjc_state = DTJ_CONSUMER_INIT; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate consumer"); + } + + return (c); +} + +void +/* ARGSUSED */ +dtj_request_destroy(void *v, void *arg) +{ + if (v) { + dtj_request_t *r = v; + dtj_string_list_destroy(r->dtjr_args); + uu_list_node_fini(r, &r->dtjr_node, g_request_pool); + bzero(v, sizeof (dtj_request_t)); + uu_free(v); + } +} + +void +/* ARGSUSED */ +dtj_program_destroy(void *v, void *arg) +{ + if (v) { + dtj_program_t *p = v; + if (p->dtjp_name) { + free((void *)p->dtjp_name); + } + uu_list_node_fini(p, &p->dtjp_node, g_program_pool); + bzero(v, sizeof (dtj_program_t)); + uu_free(v); + } +} + +void +dtj_aggval_destroy(void *v, void *arg) +{ + if (v) { + dtj_aggval_t *a = v; + if (a->dtja_value && arg) { + JNIEnv *jenv = arg; + (*jenv)->DeleteLocalRef(jenv, a->dtja_value); + } + if (a->dtja_aggname) { + free((void *)a->dtja_aggname); + } + uu_list_node_fini(a, &a->dtja_node, g_aggval_pool); + bzero(v, sizeof (dtj_aggval_t)); + uu_free(v); + } +} + +/* + * Frees per-consumer state. Assumes that the DTrace handle has been closed + * already. + */ +void +dtj_consumer_destroy(dtj_consumer_t *c) +{ + if (c) { + dtj_list_destroy(c->dtjc_request_list, dtj_request_destroy, + NULL); + (void) pthread_mutex_destroy(&c->dtjc_request_list_lock); + dtj_list_destroy(c->dtjc_program_list, dtj_program_destroy, + NULL); + /* + * Cannot dtrace_proc_release the c->process_list proc + * elements here, because we need the dtrace handle for that. + * By the time this destructor is called, the dtrace handle is + * already closed. The proc elements are released in + * dtrace_jni.c _close(). + */ + if (c->dtjc_process_list) { + dtj_list_destroy(c->dtjc_process_list, NULL, NULL); + } + bzero(c, sizeof (dtj_consumer_t)); + uu_free(c); + } +} + +void +dtj_throw_dtrace_exception(dtj_java_consumer_t *jc, const char *fmt, ...) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + va_list ap; + char msg[DTJ_MSG_SIZE]; + + jobject message = NULL; + jobject exception = NULL; + + va_start(ap, fmt); + (void) vsnprintf(msg, sizeof (msg), fmt, ap); + va_end(ap); + + message = dtj_NewStringNative(jenv, msg); + if (!message) { + return; /* java exception pending */ + } + + exception = (*jenv)->NewObject(jenv, g_dtx_jc, g_dtxinit_jm, message); + (*jenv)->DeleteLocalRef(jenv, message); + if (exception) { + (*jenv)->Throw(jenv, exception); + (*jenv)->DeleteLocalRef(jenv, exception); + } +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_probe.c b/usr/src/lib/libdtrace_jni/common/dtj_probe.c new file mode 100644 index 0000000000..9f64882653 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_probe.c @@ -0,0 +1,108 @@ +/* + * 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 +#include + +/* + * This file creates instances of the following Java classes: + * - org.opensolaris.os.dtrace.ProbeDescription + * - org.opensolaris.os.dtrace.ProbeInfo + */ + +jobject +dtj_new_probedesc(dtj_java_consumer_t *jc, const dtrace_probedesc_t *probedesc) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jstring jprov = NULL; + jstring jmod = NULL; + jstring jfunc = NULL; + jstring jname = NULL; + jobject jprobedesc = NULL; + + jprov = (*jenv)->NewStringUTF(jenv, probedesc->dtpd_provider); + if ((*jenv)->ExceptionCheck(jenv)) { + goto cleanup; + } + jmod = (*jenv)->NewStringUTF(jenv, probedesc->dtpd_mod); + if ((*jenv)->ExceptionCheck(jenv)) { + goto cleanup; + } + jfunc = (*jenv)->NewStringUTF(jenv, probedesc->dtpd_func); + if ((*jenv)->ExceptionCheck(jenv)) { + goto cleanup; + } + jname = (*jenv)->NewStringUTF(jenv, probedesc->dtpd_name); + if ((*jenv)->ExceptionCheck(jenv)) { + goto cleanup; + } + jprobedesc = (*jenv)->NewObject(jenv, g_probedesc_jc, + g_probedescinit_jm, jprov, jmod, jfunc, jname); + if ((*jenv)->ExceptionCheck(jenv)) { + goto cleanup; + } + /* Does not throw exceptions */ + (*jenv)->SetIntField(jenv, jprobedesc, g_probedesc_id_jf, + probedesc->dtpd_id); + +cleanup: + + (*jenv)->DeleteLocalRef(jenv, jprov); + (*jenv)->DeleteLocalRef(jenv, jmod); + (*jenv)->DeleteLocalRef(jenv, jfunc); + (*jenv)->DeleteLocalRef(jenv, jname); + return (jprobedesc); +} + +jobject +dtj_new_probeinfo(dtj_java_consumer_t *jc, const dtrace_probeinfo_t *probeinfo) +{ + JNIEnv *jenv = jc->dtjj_jenv; + + jobject jprobeattr = NULL; + jobject jargattr = NULL; + jobject jprobeinfo = NULL; /* return value */ + + jprobeattr = dtj_new_attribute(jc, &probeinfo->dtp_attr); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + jargattr = dtj_new_attribute(jc, &probeinfo->dtp_arga); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, jprobeattr); + return (NULL); + } + + jprobeinfo = (*jenv)->NewObject(jenv, g_probeinfo_jc, + g_probeinfoinit_jm, jprobeattr, jargattr); + + (*jenv)->DeleteLocalRef(jenv, jprobeattr); + (*jenv)->DeleteLocalRef(jenv, jargattr); + return (jprobeinfo); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_util.c b/usr/src/lib/libdtrace_jni/common/dtj_util.c new file mode 100644 index 0000000000..17463e0f0e --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_util.c @@ -0,0 +1,1506 @@ +/* + * 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 +#include +#include +#include +#include +#include + +/* + * dtj_util.c separates functionality that is generally useful from + * that which is specific to the Java DTrace API. If moved to a separate + * library, this functionality could be shared by other JNI wrappers. + */ + +boolean_t g_dtj_util_debug = B_FALSE; +static boolean_t g_dtj_load_common = B_FALSE; + +/* NativeException */ +jclass g_nx_jc = 0; +jmethodID g_nxinit_jm = 0; + +/* java.io.Serializable */ +jclass g_serial_jc = 0; + +/* java.lang.Number */ +jclass g_number_jc = 0; +jmethodID g_shortval_jm = 0; +jmethodID g_intval_jm = 0; +jmethodID g_longval_jm = 0; + +/* java.lang.Byte */ +jclass g_byte_jc = 0; +jmethodID g_byteinit_jm = 0; + +/* java.lang.Character */ +jclass g_char_jc = 0; +jmethodID g_charinit_jm = 0; +jmethodID g_charval_jm = 0; + +/* java.lang.Short */ +jclass g_short_jc = 0; +jmethodID g_shortinit_jm = 0; + +/* java.lang.Integer */ +jclass g_int_jc = 0; +jmethodID g_intinit_jm = 0; + +/* java.lang.Long */ +jclass g_long_jc = 0; +jmethodID g_longinit_jm = 0; + +/* java.lang.String */ +jclass g_string_jc = 0; +jmethodID g_strinit_bytes_jm = 0; +jmethodID g_strbytes_jm = 0; +jmethodID g_trim_jm = 0; + +/* java.lang.StringBuffer */ +jclass g_buf_jc = 0; +jmethodID g_bufinit_jm = 0; +jmethodID g_buf_append_char_jm = 0; +jmethodID g_buf_append_int_jm = 0; +jmethodID g_buf_append_long_jm = 0; +jmethodID g_buf_append_str_jm = 0; +jmethodID g_buf_append_obj_jm = 0; +jmethodID g_buflen_jm = 0; +jmethodID g_bufsetlen_jm = 0; + +/* java.lang.Object */ +jclass g_object_jc = 0; +jmethodID g_tostring_jm = 0; +jmethodID g_equals_jm = 0; + +/* java.lang.Enum */ +jclass g_enum_jc = 0; +jmethodID g_enumname_jm = 0; + +/* List */ +jclass g_list_jc = 0; +jmethodID g_listclear_jm = 0; +jmethodID g_listadd_jm = 0; +jmethodID g_listget_jm = 0; +jmethodID g_listsize_jm = 0; + +/* Global list pools */ +static uu_list_pool_t *g_pointer_pool = NULL; +static uu_list_pool_t *g_string_pool = NULL; + +static dtj_status_t dtj_get_jni_classes(JNIEnv *, uu_list_t *, uu_list_pool_t *, + uu_list_pool_t *, uu_list_pool_t *, const dtj_table_entry_t *); +static dtj_status_t dtj_cache_jni_methods(JNIEnv *, dtj_java_class_t *); +static dtj_status_t dtj_cache_jni_fields(JNIEnv *, dtj_java_class_t *); + +/* Constructors */ +static dtj_java_class_t *dtj_java_class_create(JNIEnv *, jclass *, char *, + uu_list_pool_t *, uu_list_pool_t *, uu_list_pool_t *); +static dtj_java_method_t *dtj_java_method_create(JNIEnv *, jmethodID *, char *, + char *, uu_list_pool_t *); +static dtj_java_method_t *dtj_java_static_method_create(JNIEnv *, jmethodID *, + char *, char *, uu_list_pool_t *); +static dtj_java_field_t *dtj_java_field_create(JNIEnv *, jfieldID *, char *, + char *, uu_list_pool_t *); +static dtj_java_field_t *dtj_java_static_field_create(JNIEnv *, jfieldID *, + char *, char *, uu_list_pool_t *); + +/* Destructors */ +static void dtj_java_class_destroy(void *, void *); +static void dtj_java_method_destroy(void *, void *); +static void dtj_java_field_destroy(void *, void *); + +/* Comparison functions, uu_compare_fn_t signature */ +static int dtj_java_class_cmp(const void *, const void *, void *); +static int dtj_java_method_cmp(const void *, const void *, void *); +static int dtj_java_field_cmp(const void *, const void *, void *); + +/* Java Throwable */ +static void dtj_throw(JNIEnv *, jclass, const char *, va_list *); + +/* Support for uu_list_t wrappers */ +static boolean_t dtj_check_pointer_pool(void); +static boolean_t dtj_check_string_pool(void); + +dtj_status_t +dtj_load_common(JNIEnv *jenv) +{ + dtj_status_t status; + + static const dtj_table_entry_t table[] = { + /* NativeException */ + { JCLASS, &g_nx_jc, + "org/opensolaris/os/dtrace/NativeException" }, + { JMETHOD, &g_nxinit_jm, CONSTRUCTOR, + "(Ljava/lang/String;ILjava/lang/Throwable;)V" }, + + /* java.io.Serializable */ + { JCLASS, &g_serial_jc, "java/io/Serializable" }, + + /* java.lang.Number */ + { JCLASS, &g_number_jc, "java/lang/Number" }, + { JMETHOD, &g_shortval_jm, "shortValue", "()S" }, + { JMETHOD, &g_intval_jm, "intValue", "()I" }, + { JMETHOD, &g_longval_jm, "longValue", "()J" }, + + /* java.lang.Byte */ + { JCLASS, &g_byte_jc, "java/lang/Byte" }, + { JMETHOD, &g_byteinit_jm, CONSTRUCTOR, "(B)V" }, + + /* java.lang.Character */ + { JCLASS, &g_char_jc, "java/lang/Character" }, + { JMETHOD, &g_charinit_jm, CONSTRUCTOR, "(C)V" }, + { JMETHOD, &g_charval_jm, "charValue", "()C" }, + + /* java.lang.Short */ + { JCLASS, &g_short_jc, "java/lang/Short" }, + { JMETHOD, &g_shortinit_jm, CONSTRUCTOR, "(S)V" }, + + /* java.lang.Integer */ + { JCLASS, &g_int_jc, "java/lang/Integer" }, + { JMETHOD, &g_intinit_jm, CONSTRUCTOR, "(I)V" }, + + /* java.lang.Long */ + { JCLASS, &g_long_jc, "java/lang/Long" }, + { JMETHOD, &g_longinit_jm, CONSTRUCTOR, "(J)V" }, + + /* java.lang.String */ + { JCLASS, &g_string_jc, "java/lang/String" }, + { JMETHOD, &g_strinit_bytes_jm, CONSTRUCTOR, "([B)V" }, + { JMETHOD, &g_strbytes_jm, "getBytes", "()[B" }, + { JMETHOD, &g_trim_jm, "trim", "()Ljava/lang/String;" }, + + /* java.lang.StringBuffer */ + { JCLASS, &g_buf_jc, "java/lang/StringBuffer" }, + { JMETHOD, &g_bufinit_jm, CONSTRUCTOR, "()V" }, + { JMETHOD, &g_buf_append_char_jm, "append", + "(C)Ljava/lang/StringBuffer;" }, + { JMETHOD, &g_buf_append_int_jm, "append", + "(I)Ljava/lang/StringBuffer;" }, + { JMETHOD, &g_buf_append_long_jm, "append", + "(J)Ljava/lang/StringBuffer;" }, + { JMETHOD, &g_buf_append_str_jm, "append", + "(Ljava/lang/String;)Ljava/lang/StringBuffer;" }, + { JMETHOD, &g_buf_append_obj_jm, "append", + "(Ljava/lang/Object;)Ljava/lang/StringBuffer;" }, + { JMETHOD, &g_buflen_jm, "length", "()I" }, + { JMETHOD, &g_bufsetlen_jm, "setLength", "(I)V" }, + + /* java.lang.Object */ + { JCLASS, &g_object_jc, "java/lang/Object" }, + { JMETHOD, &g_tostring_jm, "toString", + "()Ljava/lang/String;" }, + { JMETHOD, &g_equals_jm, "equals", + "(Ljava/lang/Object;)Z" }, + + /* java.lang.Enum */ + { JCLASS, &g_enum_jc, "java/lang/Enum" }, + { JMETHOD, &g_enumname_jm, "name", + "()Ljava/lang/String;" }, + + /* List */ + { JCLASS, &g_list_jc, "java/util/List" }, + { JMETHOD, &g_listclear_jm, "clear", "()V" }, + { JMETHOD, &g_listadd_jm, "add", "(Ljava/lang/Object;)Z" }, + { JMETHOD, &g_listget_jm, "get", "(I)Ljava/lang/Object;" }, + { JMETHOD, &g_listsize_jm, "size", "()I" }, + + { DTJ_TYPE_END } + }; + + status = dtj_cache_jni_classes(jenv, table); + if (status == DTJ_OK) { + g_dtj_load_common = B_TRUE; + } + return (status); +} + +static int +/* ARGSUSED */ +dtj_java_class_cmp(const void * v1, const void * v2, void *arg) +{ + const dtj_java_class_t *c1 = v1; + const dtj_java_class_t *c2 = v2; + return (strcmp(c1->djc_name, c2->djc_name)); +} + +static int +/* ARGSUSED */ +dtj_java_method_cmp(const void *v1, const void *v2, void *arg) +{ + int cmp; + const dtj_java_method_t *m1 = v1; + const dtj_java_method_t *m2 = v2; + cmp = strcmp(m1->djm_name, m2->djm_name); + if (cmp == 0) { + cmp = strcmp(m1->djm_signature, m2->djm_signature); + } + return (cmp); +} + +static int +/* ARGSUSED */ +dtj_java_field_cmp(const void *v1, const void *v2, void *arg) +{ + const dtj_java_field_t *f1 = v1; + const dtj_java_field_t *f2 = v2; + return (strcmp(f1->djf_name, f2->djf_name)); +} + +static dtj_java_class_t * +dtj_java_class_create(JNIEnv *jenv, jclass *jc, char *name, + uu_list_pool_t *classpool, uu_list_pool_t *methodpool, + uu_list_pool_t *fieldpool) +{ + dtj_java_class_t *c = uu_zalloc(sizeof (dtj_java_class_t)); + if (c) { + uu_list_node_init(c, &c->djc_node, classpool); + c->djc_ptr = jc; + c->djc_name = name; + c->djc_methods = uu_list_create(methodpool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!c->djc_methods) { + dtj_throw_out_of_memory(jenv, + "Failed method list creation"); + uu_list_node_fini(c, &c->djc_node, classpool); + free(c); + c = NULL; + } + c->djc_fields = uu_list_create(fieldpool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!c->djc_fields) { + dtj_throw_out_of_memory(jenv, + "Failed field list creation"); + uu_list_destroy(c->djc_methods); + c->djc_methods = NULL; + uu_list_node_fini(c, &c->djc_node, classpool); + free(c); + c = NULL; + } + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate class description"); + } + return (c); +} + +static dtj_java_method_t * +dtj_java_method_create(JNIEnv *jenv, jmethodID *jm, char *name, char *signature, + uu_list_pool_t *methodpool) +{ + dtj_java_method_t *m = uu_zalloc(sizeof (dtj_java_method_t)); + if (m) { + uu_list_node_init(m, &m->djm_node, methodpool); + m->djm_ptr = jm; + m->djm_name = name; + m->djm_signature = signature; + m->djm_static = B_FALSE; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate method description"); + } + return (m); +} + +static dtj_java_method_t * +dtj_java_static_method_create(JNIEnv *jenv, jmethodID *jm, char *name, + char *signature, uu_list_pool_t *methodpool) +{ + dtj_java_method_t *m = dtj_java_method_create(jenv, jm, name, signature, + methodpool); + if (m) { + m->djm_static = B_TRUE; + } + return (m); +} + +static dtj_java_field_t * +dtj_java_field_create(JNIEnv *jenv, jfieldID *jf, char *name, char *type, + uu_list_pool_t *fieldpool) +{ + dtj_java_field_t *f = uu_zalloc(sizeof (dtj_java_field_t)); + if (f) { + uu_list_node_init(f, &f->djf_node, fieldpool); + f->djf_ptr = jf; + f->djf_name = name; + f->djf_type = type; + f->djf_static = B_FALSE; + } else { + dtj_throw_out_of_memory(jenv, + "Failed to allocate field description"); + } + return (f); +} + +static dtj_java_field_t * +dtj_java_static_field_create(JNIEnv *jenv, jfieldID *jf, char *name, char *type, + uu_list_pool_t *fieldpool) +{ + dtj_java_field_t *f = dtj_java_field_create(jenv, jf, name, type, + fieldpool); + if (f) { + f->djf_static = B_TRUE; + } + return (f); +} + +static void +/* ARGSUSED */ +dtj_java_class_destroy(void *v, void *arg) +{ + if (v) { + dtj_java_class_t *c = v; + c->djc_ptr = NULL; /* do not free user-defined storage */ + c->djc_name = NULL; /* string literal */ + dtj_list_destroy(c->djc_methods, dtj_java_method_destroy, NULL); + dtj_list_destroy(c->djc_fields, dtj_java_field_destroy, NULL); + c->djc_methods = NULL; + c->djc_fields = NULL; + uu_free(v); + } +} + +static void +/* ARGSUSED */ +dtj_java_method_destroy(void *v, void *arg) +{ + if (v) { + dtj_java_method_t *m = v; + m->djm_ptr = NULL; /* do not free user-defined space */ + m->djm_name = NULL; /* string literal */ + m->djm_signature = NULL; /* string literal */ + uu_free(v); + } +} + +static void +/* ARGSUSED */ +dtj_java_field_destroy(void *v, void *arg) +{ + if (v) { + dtj_java_field_t *f = v; + f->djf_ptr = NULL; /* do not free user-defined space */ + f->djf_name = NULL; /* string literal */ + f->djf_type = NULL; /* string literal */ + uu_free(f); + } +} + +dtj_status_t +dtj_cache_jni_classes(JNIEnv *jenv, const dtj_table_entry_t *table) +{ + dtj_java_class_t *class; + uu_list_pool_t *classpool; + uu_list_pool_t *methodpool; + uu_list_pool_t *fieldpool; + uu_list_t *classes; + uu_list_walk_t *itr; + jclass jc; + jclass gjc; + dtj_status_t status; + + classpool = uu_list_pool_create("classpool", + sizeof (dtj_java_class_t), + offsetof(dtj_java_class_t, djc_node), dtj_java_class_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!classpool) { + dtj_throw_out_of_memory(jenv, "failed class pool creation"); + return (DTJ_ERR); + } + methodpool = uu_list_pool_create("methodpool", + sizeof (dtj_java_method_t), + offsetof(dtj_java_method_t, djm_node), dtj_java_method_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!methodpool) { + dtj_throw_out_of_memory(jenv, "failed method pool creation"); + return (DTJ_ERR); + } + fieldpool = uu_list_pool_create("fieldpool", + sizeof (dtj_java_field_t), + offsetof(dtj_java_field_t, djf_node), dtj_java_field_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (!fieldpool) { + dtj_throw_out_of_memory(jenv, "failed field pool creation"); + return (DTJ_ERR); + } + + classes = uu_list_create(classpool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + if (!classes) { + dtj_throw_out_of_memory(jenv, "failed class list creation"); + return (DTJ_ERR); + } + + status = dtj_get_jni_classes(jenv, classes, classpool, methodpool, + fieldpool, table); + if (status != DTJ_OK) { + /* java error pending */ + return (status); + } + + itr = uu_list_walk_start(classes, 0); + while ((class = uu_list_walk_next(itr)) != NULL) { + jc = (*jenv)->FindClass(jenv, class->djc_name); + if (!jc) { + /* NoClassDefFoundError pending */ + return (DTJ_ERR); + } + gjc = (*jenv)->NewGlobalRef(jenv, jc); + (*jenv)->DeleteLocalRef(jenv, jc); + if (!gjc) { + dtj_throw_out_of_memory(jenv, + "Failed to create global class reference"); + return (DTJ_ERR); + } + *(class->djc_ptr) = gjc; + status = dtj_cache_jni_methods(jenv, class); + if (status != DTJ_OK) { + /* java error pending */ + return (status); + } + status = dtj_cache_jni_fields(jenv, class); + if (status != DTJ_OK) { + /* java error pending */ + return (status); + } + } + uu_list_walk_end(itr); + dtj_list_destroy(classes, dtj_java_class_destroy, NULL); + uu_list_pool_destroy(classpool); + uu_list_pool_destroy(methodpool); + uu_list_pool_destroy(fieldpool); + return (DTJ_OK); +} + +/* + * Converts JNI table entry desriptions into java_class_t descriptors. + */ +static dtj_status_t +dtj_get_jni_classes(JNIEnv *jenv, uu_list_t *classes, + uu_list_pool_t *classpool, uu_list_pool_t *methodpool, + uu_list_pool_t *fieldpool, const dtj_table_entry_t *table) +{ + int i; + dtj_java_class_t *c = NULL; + dtj_java_method_t *m; + dtj_java_field_t *f; + + for (i = 0; table[i].djte_type != DTJ_TYPE_END; ++i) { + /* + * Class not added until all of its method and field information + * is attached, so we defer adding a class until the next + * element with type JCLASS. + */ + switch (table[i].djte_type) { + case JCLASS: + if (c) { + /* previous class */ + if (!dtj_list_add(classes, c)) { + dtj_throw_out_of_memory(jenv, + "Failed to add class description"); + /* + * In response to an error return value, + * the caller will delete the class + * descriptions list with any + * descriptions created so far. + */ + return (DTJ_ERR); + } + } + c = dtj_java_class_create(jenv, + (jclass *)table[i].djte_addr, table[i].djte_name, + classpool, methodpool, fieldpool); + if (!c) { + /* OutOfMemoryError pending */ + return (DTJ_ERR); + } + break; + case JMETHOD: + if (!c) { + dtj_throw_illegal_state(jenv, + "method description not preceded " + "by class description"); + return (DTJ_ERR); + } + m = dtj_java_method_create(jenv, + (jmethodID *)table[i].djte_addr, + table[i].djte_name, table[i].djte_desc, + methodpool); + if (!m) { + /* OutOfMemoryError pending */ + return (DTJ_ERR); + } + if (!dtj_list_add(c->djc_methods, m)) { + dtj_throw_out_of_memory(jenv, + "Failed to add method description"); + return (DTJ_ERR); + } + break; + case JMETHOD_STATIC: + if (!c) { + dtj_throw_illegal_state(jenv, + "static method description not preceded " + "by class description"); + return (DTJ_ERR); + } + m = dtj_java_static_method_create(jenv, + (jmethodID *)table[i].djte_addr, + table[i].djte_name, table[i].djte_desc, + methodpool); + if (!m) { + /* OutOfMemoryError pending */ + return (DTJ_ERR); + } + if (!dtj_list_add(c->djc_methods, m)) { + dtj_throw_out_of_memory(jenv, + "Failed to add static method description"); + return (DTJ_ERR); + } + break; + case JFIELD: + if (!c) { + dtj_throw_illegal_state(jenv, + "field description not preceded " + "by class description"); + return (DTJ_ERR); + } + f = dtj_java_field_create(jenv, + (jfieldID *)table[i].djte_addr, + table[i].djte_name, table[i].djte_desc, + fieldpool); + if (!f) { + /* OutOfMemoryError pending */ + return (DTJ_ERR); + } + if (!dtj_list_add(c->djc_fields, f)) { + dtj_throw_out_of_memory(jenv, + "Failed to add field description"); + return (DTJ_ERR); + } + break; + case JFIELD_STATIC: + if (!c) { + dtj_throw_illegal_state(jenv, + "static field description not preceded " + "by class description"); + return (DTJ_ERR); + } + f = dtj_java_static_field_create(jenv, + (jfieldID *)table[i].djte_addr, + table[i].djte_name, table[i].djte_desc, + fieldpool); + if (!f) { + /* OutOfMemoryError pending */ + return (DTJ_ERR); + } + if (!dtj_list_add(c->djc_fields, f)) { + dtj_throw_out_of_memory(jenv, + "Failed to add static field description"); + return (DTJ_ERR); + } + break; + default: + dtj_throw_illegal_state(jenv, + "Unexpected jni_type_e: %d", table[i].djte_type); + return (DTJ_ERR); + } + } + if (c) { + /* last class */ + if (!dtj_list_add(classes, c)) { + dtj_throw_out_of_memory(jenv, + "Failed to add class description"); + return (DTJ_ERR); + } + } + + return (DTJ_OK); +} + +static dtj_status_t +dtj_cache_jni_methods(JNIEnv *jenv, dtj_java_class_t *c) +{ + dtj_java_method_t *method; + jmethodID jm; + uu_list_walk_t *itr; + itr = uu_list_walk_start(c->djc_methods, 0); + while ((method = uu_list_walk_next(itr)) != NULL) { + if (method->djm_static) { + jm = (*jenv)->GetStaticMethodID(jenv, *(c->djc_ptr), + method->djm_name, method->djm_signature); + } else { + jm = (*jenv)->GetMethodID(jenv, *(c->djc_ptr), + method->djm_name, method->djm_signature); + } + if (jm == 0) { + /* + * The pending NoSuchMethodError gives only the + * method name, which is not so helpful for + * overloaded methods and methods such as + * that have the same name in multiple classes. + * Clear the pending error and throw one that + * includes the class name and the method + * signature. + */ + jclass jc; + char msg[DTJ_MSG_SIZE]; + (*jenv)->ExceptionClear(jenv); + (void) snprintf(msg, sizeof (msg), "%s %s %s", + c->djc_name, method->djm_name, + method->djm_signature); + + jc = (*jenv)->FindClass(jenv, + "java/lang/NoSuchMethodError"); + (*jenv)->ThrowNew(jenv, jc, msg); + (*jenv)->DeleteLocalRef(jenv, jc); + return (DTJ_ERR); + } + *(method->djm_ptr) = jm; + } + uu_list_walk_end(itr); + return (DTJ_OK); +} + +static dtj_status_t +dtj_cache_jni_fields(JNIEnv *jenv, dtj_java_class_t *c) +{ + dtj_java_field_t *field; + jfieldID jf; + uu_list_walk_t *itr; + itr = uu_list_walk_start(c->djc_fields, 0); + while ((field = uu_list_walk_next(itr)) != NULL) { + if (field->djf_static) { + jf = (*jenv)->GetStaticFieldID(jenv, *(c->djc_ptr), + field->djf_name, field->djf_type); + } else { + jf = (*jenv)->GetFieldID(jenv, *(c->djc_ptr), + field->djf_name, field->djf_type); + } + if (jf == 0) { + jclass jc; + char msg[DTJ_MSG_SIZE]; + (*jenv)->ExceptionClear(jenv); + (void) snprintf(msg, sizeof (msg), + "%s.%s signature: %s", c->djc_name, + field->djf_name, field->djf_type); + + jc = (*jenv)->FindClass(jenv, + "java/lang/NoSuchFieldError"); + (*jenv)->ThrowNew(jenv, jc, msg); + (*jenv)->DeleteLocalRef(jenv, jc); + return (DTJ_ERR); + } + *(field->djf_ptr) = jf; + } + uu_list_walk_end(itr); + return (DTJ_OK); +} + + +/* Common utilities */ + +static void +dtj_throw(JNIEnv *jenv, jclass jc, const char *fmt, va_list *ap) +{ + char msg[DTJ_MSG_SIZE]; + (void) vsnprintf(msg, sizeof (msg), fmt, *ap); + (*jenv)->ThrowNew(jenv, jc, msg); +} + +void +dtj_throw_out_of_memory(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc; + /* + * JNI documentation unclear whether NewGlobalRef() can throw + * OutOfMemoryError, so we'll make this function safe in case + * OutOfMemoryError has already been thrown + */ + if ((*jenv)->ExceptionCheck(jenv)) { + return; + } + jc = (*jenv)->FindClass(jenv, + "java/lang/OutOfMemoryError"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_null_pointer(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/lang/NullPointerException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_illegal_state(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/lang/IllegalStateException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_illegal_argument(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/lang/IllegalArgumentException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_no_such_element(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/util/NoSuchElementException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_class_cast(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/lang/ClassCastException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_assertion(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "java/lang/AssertionError"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_throw_resource_limit(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + jclass jc = (*jenv)->FindClass(jenv, + "org/opensolaris/os/dtrace/ResourceLimitException"); + va_start(ap, fmt); + dtj_throw(jenv, jc, fmt, &ap); + (*jenv)->DeleteLocalRef(jenv, jc); + va_end(ap); +} + +void +dtj_wrap_exception(JNIEnv *jenv, const char *file, int line) +{ + jthrowable e = NULL; + jthrowable nx = NULL; + jstring jfile = NULL; + + e = (*jenv)->ExceptionOccurred(jenv); + if (!e) { + return; + } + + if (!g_dtj_load_common) { + return; + } + + (*jenv)->ExceptionClear(jenv); + + /* Unsafe to test while exception pending */ + if ((*jenv)->IsInstanceOf(jenv, e, g_nx_jc)) { + /* Already wrapped */ + (*jenv)->Throw(jenv, e); + (*jenv)->DeleteLocalRef(jenv, e); + return; + } + + jfile = dtj_NewStringNative(jenv, file); + if ((*jenv)->ExceptionCheck(jenv)) { + /* + * Only wrap the exception if possible, otherwise just throw the + * original exception. + */ + (*jenv)->ExceptionClear(jenv); + (*jenv)->Throw(jenv, e); + (*jenv)->DeleteLocalRef(jenv, e); + return; + } + + nx = (jthrowable)(*jenv)->NewObject(jenv, g_nx_jc, g_nxinit_jm, + jfile, line, e); + (*jenv)->DeleteLocalRef(jenv, jfile); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->ExceptionClear(jenv); + (*jenv)->Throw(jenv, e); + (*jenv)->DeleteLocalRef(jenv, e); + return; + } + + (*jenv)->DeleteLocalRef(jenv, e); + (*jenv)->Throw(jenv, nx); + (*jenv)->DeleteLocalRef(jenv, nx); +} + +/* + * Calls the given java object's toString() method and prints the value to + * stdout. Useful for debugging. Guaranteed that no exception is pending when + * this function returns. + */ +void +dtj_print_object(JNIEnv *jenv, jobject jobj) +{ + jstring jstr; + const char *cstr; + + if (!g_dtj_load_common) { + dtj_throw_illegal_state(jenv, + "dtj_load_common() has not been called"); + (*jenv)->ExceptionDescribe(jenv); /* clears the exception */ + return; + } + + if (!jobj) { + (void) printf("null\n"); + return; + } + + jstr = (*jenv)->CallObjectMethod(jenv, jobj, g_tostring_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->ExceptionDescribe(jenv); /* clears the exception */ + return; + } + cstr = (*jenv)->GetStringUTFChars(jenv, jstr, 0); + if (cstr) { + (void) printf("%s\n", cstr); + } else { + (*jenv)->ExceptionDescribe(jenv); /* clears the exception */ + (*jenv)->DeleteLocalRef(jenv, jstr); + return; + } + (*jenv)->ReleaseStringUTFChars(jenv, jstr, cstr); + (*jenv)->DeleteLocalRef(jenv, jstr); +} + +jstring +dtj_format_string(JNIEnv *jenv, const char *fmt, ...) +{ + va_list ap; + char str[DTJ_MSG_SIZE]; + + jstring jstr = NULL; + + va_start(ap, fmt); + (void) vsnprintf(str, sizeof (str), fmt, ap); + va_end(ap); + + jstr = dtj_NewStringNative(jenv, str); + /* return NULL if OutOfMemoryError pending */ + return (jstr); +} + +jstring +dtj_NewStringNative(JNIEnv *jenv, const char *str) +{ + jstring result; + jbyteArray bytes = 0; + int len; + + if (!g_dtj_load_common) { + dtj_throw_illegal_state(jenv, + "dtj_load_common() has not been called"); + return (NULL); + } + + len = strlen(str); + + bytes = (*jenv)->NewByteArray(jenv, len); + if (!bytes) { + return (NULL); /* OutOfMemoryError pending */ + } + (*jenv)->SetByteArrayRegion(jenv, bytes, 0, len, + (jbyte *)str); + if ((*jenv)->ExceptionCheck(jenv)) { + (*jenv)->DeleteLocalRef(jenv, bytes); + return (NULL); /* ArrayIndexOutOfBoundsException pending */ + } + result = (*jenv)->NewObject(jenv, g_string_jc, g_strinit_bytes_jm, + bytes); + (*jenv)->DeleteLocalRef(jenv, bytes); + /* return NULL result if exception pending */ + return (result); +} + +char * +dtj_GetStringNativeChars(JNIEnv *jenv, jstring jstr) +{ + jbyteArray bytes = NULL; + + jint len; + char *result = NULL; + + if (!g_dtj_load_common) { + dtj_throw_illegal_state(jenv, + "dtj_load_common() has not been called"); + return (NULL); + } + + bytes = (*jenv)->CallObjectMethod(jenv, jstr, g_strbytes_jm); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); /* OutOfMemoryError pending */ + } + /* Does not throw exceptions */ + len = (*jenv)->GetArrayLength(jenv, bytes); + result = malloc(len + 1); + if (!result) { + (*jenv)->DeleteLocalRef(jenv, bytes); + dtj_throw_out_of_memory(jenv, + "could not allocate native chars"); + return (NULL); + } + + /* Skip check for ArrayIndexOutOfBoundsException */ + (*jenv)->GetByteArrayRegion(jenv, bytes, 0, len, + (jbyte *)result); + (*jenv)->DeleteLocalRef(jenv, bytes); + result[len] = '\0'; /* NUL-terminate */ + + return (result); +} + +void +/* ARGSUSED */ +dtj_ReleaseStringNativeChars(JNIEnv *jenv, jstring jstr, const char *str) +{ + free((void *)str); +} + +char ** +dtj_get_argv(JNIEnv *jenv, jobjectArray args, int *argc) +{ + char **argv = NULL; /* return value */ + const char *str; + int i; + + jstring jstr = NULL; + + if (!g_dtj_load_common) { + dtj_throw_illegal_state(jenv, + "dtj_load_common() has not been called"); + return (NULL); + } + + *argc = (*jenv)->GetArrayLength(jenv, args); + /* + * Initialize all string pointers to NULL so that in case of an error + * filling in the array, free_argv() will not attempt to free the + * unallocated elements. Also NULL-terminate the string array for + * functions that expect terminating NULL rather than rely on argc. + */ + argv = uu_zalloc((sizeof (char *)) * (*argc + 1)); + if (!argv) { + dtj_throw_out_of_memory(jenv, "Failed to allocate args array"); + return (NULL); + } + + for (i = 0; i < *argc; ++i) { + jstr = (*jenv)->GetObjectArrayElement(jenv, args, i); + if ((*jenv)->ExceptionCheck(jenv)) { + dtj_free_argv(argv); + return (NULL); + } + str = dtj_GetStringNativeChars(jenv, jstr); + if ((*jenv)->ExceptionCheck(jenv)) { + dtj_free_argv(argv); + (*jenv)->DeleteLocalRef(jenv, jstr); + return (NULL); + } + argv[i] = malloc(strlen(str) + 1); + if (!argv[i]) { + dtj_throw_out_of_memory(jenv, "Failed to allocate arg"); + dtj_free_argv(argv); + dtj_ReleaseStringNativeChars(jenv, jstr, str); + (*jenv)->DeleteLocalRef(jenv, jstr); + return (NULL); + } + (void) strcpy(argv[i], str); + dtj_ReleaseStringNativeChars(jenv, jstr, str); + (*jenv)->DeleteLocalRef(jenv, jstr); + jstr = NULL; + } + + return (argv); +} + +char ** +dtj_make_argv(JNIEnv *jenv, jstring command, int *argc) +{ + const char *ws = "\f\n\r\t\v "; + char **argv = NULL; /* return value */ + const char *cmd; /* native command string */ + char *s; /* writable command */ + char *tok; /* token */ + int len; + + if (!g_dtj_load_common) { + dtj_throw_illegal_state(jenv, + "dtj_load_common() has not been called"); + return (NULL); + } + + if (!command) { + dtj_throw_null_pointer(jenv, "command is null"); + return (NULL); + } else if ((*jenv)->GetStringLength(jenv, command) == 0) { + dtj_throw_illegal_argument(jenv, "command is empty"); + return (NULL); + } + + cmd = dtj_GetStringNativeChars(jenv, command); + if ((*jenv)->ExceptionCheck(jenv)) { + return (NULL); + } + len = strlen(cmd); + s = malloc(len + 1); + if (!s) { + dtj_throw_out_of_memory(jenv, + "failed to allocate command string"); + dtj_ReleaseStringNativeChars(jenv, command, cmd); + return (NULL); + } + (void) strcpy(s, cmd); + /* + * Initialize all string pointers to NULL so that in case of an error + * filling in the array, free_argv() will not attempt to free the + * unallocated elements. Also NULL-terminate the string array for + * functions that expect terminating NULL rather than rely on argc. + * Allow for maximum length resulting from single-character tokens + * separated by single spaces. + */ + argv = uu_zalloc(sizeof (char *) * (len / 2 + 1)); + if (!argv) { + dtj_throw_out_of_memory(jenv, "failed to allocate args array"); + free(s); + dtj_ReleaseStringNativeChars(jenv, command, cmd); + return (NULL); + } + + *argc = 0; + for (tok = strtok(s, ws); tok != NULL; tok = strtok(NULL, ws)) { + argv[*argc] = malloc(strlen(tok) + 1); + if (!argv[*argc]) { + dtj_throw_out_of_memory(jenv, "Failed to allocate arg"); + dtj_free_argv(argv); + free(s); + dtj_ReleaseStringNativeChars(jenv, command, cmd); + return (NULL); + } + (void) strcpy(argv[(*argc)++], tok); + } + + if (*argc == 0) { + dtj_throw_illegal_argument(jenv, "command is blank"); + dtj_free_argv(argv); + free(s); + dtj_ReleaseStringNativeChars(jenv, command, cmd); + return (NULL); + } + + free(s); + dtj_ReleaseStringNativeChars(jenv, command, cmd); + return (argv); +} + +void +dtj_free_argv(char **argv) +{ + if (argv) { + char **s = argv; + while (*s) { + free((void *)*s); + *s++ = NULL; + } + free((void *)argv); + } +} + + +/* Wrappers for uu_list_t */ + +int +/* ARGSUSED */ +dtj_pointer_list_entry_cmp(const void *v1, const void *v2, void *arg) +{ + const dtj_pointer_list_entry_t *p1 = v1; + const dtj_pointer_list_entry_t *p2 = v2; + + /* + * It is not valid to compare pointers using the relational operators + * unless they point to elements in the same array. + */ + uint64_t x = (uint64_t)p1->dple_ptr; + uint64_t y = (uint64_t)p2->dple_ptr; + int rc; + rc = ((x > y) ? 1 : ((x < y) ? -1 : 0)); + return (rc); +} + +int +/* ARGSUSED */ +dtj_string_list_entry_cmp(const void *v1, const void *v2, void *arg) +{ + const dtj_string_list_entry_t *p1 = v1; + const dtj_string_list_entry_t *p2 = v2; + const char *s1 = p1->dsle_value; + const char *s2 = p2->dsle_value; + if (s1 == NULL) { + return (s2 == NULL ? 0 : -1); + } + if (s2 == NULL) { + return (1); + } + return (strcmp(s1, s2)); +} + +static boolean_t +dtj_check_pointer_pool(void) +{ + if (g_pointer_pool == NULL) { + g_pointer_pool = uu_list_pool_create("g_pointer_pool", + sizeof (dtj_pointer_list_entry_t), + offsetof(dtj_pointer_list_entry_t, dple_node), + dtj_pointer_list_entry_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (g_pointer_pool == NULL) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +uu_list_t * +dtj_pointer_list_create(void) +{ + uu_list_t *list; + + if (!dtj_check_pointer_pool()) { + return (NULL); + } + + list = uu_list_create(g_pointer_pool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + return (list); +} + +dtj_pointer_list_entry_t * +dtj_pointer_list_entry_create(void *p) +{ + dtj_pointer_list_entry_t *e; + + if (!dtj_check_pointer_pool()) { + return (NULL); + } + + e = uu_zalloc(sizeof (dtj_pointer_list_entry_t)); + if (e) { + uu_list_node_init(e, &e->dple_node, g_pointer_pool); + e->dple_ptr = p; + } + return (e); +} + +static boolean_t +dtj_check_string_pool(void) +{ + if (g_string_pool == NULL) { + g_string_pool = uu_list_pool_create("g_string_pool", + sizeof (dtj_string_list_entry_t), + offsetof(dtj_string_list_entry_t, dsle_node), + dtj_string_list_entry_cmp, + (g_dtj_util_debug ? UU_LIST_POOL_DEBUG : 0)); + if (g_string_pool == NULL) { + return (B_FALSE); + } + } + return (B_TRUE); +} + +uu_list_t * +dtj_string_list_create(void) +{ + uu_list_t *list; + + if (!dtj_check_string_pool()) { + return (NULL); + } + + list = uu_list_create(g_string_pool, NULL, + (g_dtj_util_debug ? UU_LIST_DEBUG : 0)); + return (list); +} + +dtj_string_list_entry_t * +dtj_string_list_entry_create(const char *s) +{ + dtj_string_list_entry_t *e; + + if (!dtj_check_string_pool()) { + return (NULL); + } + + e = uu_zalloc(sizeof (dtj_string_list_entry_t)); + if (e) { + uu_list_node_init(e, &e->dsle_node, g_string_pool); + if (s) { + e->dsle_value = malloc(strlen(s) + 1); + if (e->dsle_value) { + (void) strcpy(e->dsle_value, s); + } else { + uu_list_node_fini(e, &e->dsle_node, + g_string_pool); + uu_free(e); + e = NULL; + } + } + } + return (e); +} + +void +dtj_pointer_list_entry_destroy(void *v, + dtj_value_destroy_f *value_destroy, void *arg) +{ + if (v) { + dtj_pointer_list_entry_t *e = v; + if (value_destroy) { + value_destroy(e->dple_ptr, arg); + } + uu_list_node_fini(e, &e->dple_node, g_pointer_pool); + e->dple_ptr = NULL; + uu_free(v); + } +} + +void +/* ARGSUSED */ +dtj_string_list_entry_destroy(void *v, void *arg) +{ + if (v) { + dtj_string_list_entry_t *e = v; + free(e->dsle_value); + uu_list_node_fini(e, &e->dsle_node, g_string_pool); + e->dsle_value = NULL; + uu_free(v); + } +} + +void +dtj_list_clear(uu_list_t *list, dtj_value_destroy_f *value_destroy, + void *arg) +{ + void *cookie; /* needed for uu_list_teardown */ + void *value; + + if (!list) { + return; + } + + cookie = NULL; + if (value_destroy) { + while ((value = uu_list_teardown(list, &cookie)) != NULL) { + value_destroy(value, arg); + } + } else { + while ((value = uu_list_teardown(list, &cookie)) != NULL) { + } + } +} + +void +dtj_list_destroy(uu_list_t *list, + dtj_value_destroy_f *value_destroy, void *arg) +{ + dtj_list_clear(list, value_destroy, arg); + uu_list_destroy(list); +} + +void +dtj_pointer_list_clear(uu_list_t *list, + dtj_value_destroy_f *value_destroy, void *arg) +{ + void *cookie; /* needed for uu_list_teardown */ + dtj_pointer_list_entry_t *e; + + if (!list) { + return; + } + + cookie = NULL; + while ((e = uu_list_teardown(list, &cookie)) != NULL) { + dtj_pointer_list_entry_destroy(e, value_destroy, arg); + } +} + +void +dtj_pointer_list_destroy(uu_list_t *list, + dtj_value_destroy_f *value_destroy, void *arg) +{ + dtj_pointer_list_clear(list, value_destroy, arg); + uu_list_destroy(list); +} + +void +dtj_string_list_clear(uu_list_t *list) +{ + dtj_list_clear(list, dtj_string_list_entry_destroy, NULL); +} + +void +dtj_string_list_destroy(uu_list_t *list) +{ + dtj_list_destroy(list, dtj_string_list_entry_destroy, NULL); +} + +boolean_t +dtj_list_empty(uu_list_t *list) +{ + return (uu_list_numnodes(list) == 0); +} + +boolean_t +dtj_list_add(uu_list_t *list, void *value) +{ + return (uu_list_insert_before(list, NULL, value) == 0); +} + +boolean_t +dtj_pointer_list_add(uu_list_t *list, void *p) +{ + dtj_pointer_list_entry_t *e = dtj_pointer_list_entry_create(p); + if (!e) { + return (B_FALSE); + } + return (dtj_list_add(list, e)); +} + +void * +dtj_pointer_list_walk_next(uu_list_walk_t *itr) +{ + dtj_pointer_list_entry_t *e = uu_list_walk_next(itr); + if (!e) { + return (DTJ_INVALID_PTR); + } + return (e->dple_ptr); +} + +void * +dtj_pointer_list_first(uu_list_t *list) +{ + dtj_pointer_list_entry_t *e = uu_list_first(list); + if (!e) { + /* NULL is a valid value; use -1 for invalid */ + return (DTJ_INVALID_PTR); + } + return (e->dple_ptr); +} + +void * +dtj_pointer_list_last(uu_list_t *list) +{ + dtj_pointer_list_entry_t *e = uu_list_last(list); + if (!e) { + /* NULL is a valid value; use -1 for invalid */ + return (DTJ_INVALID_PTR); + } + return (e->dple_ptr); +} + +boolean_t +dtj_string_list_add(uu_list_t *list, const char *s) +{ + dtj_string_list_entry_t *e = dtj_string_list_entry_create(s); + if (!e) { + return (B_FALSE); + } + return (dtj_list_add(list, e)); +} + +const char * +dtj_string_list_walk_next(uu_list_walk_t *itr) +{ + dtj_string_list_entry_t *e = uu_list_walk_next(itr); + if (!e) { + return (DTJ_INVALID_STR); + } + return (e->dsle_value); +} + +const char * +dtj_string_list_first(uu_list_t *list) +{ + dtj_string_list_entry_t *e = uu_list_first(list); + if (!e) { + /* NULL is a valid string value; use -1 for invalid */ + return (DTJ_INVALID_STR); + } + return (e->dsle_value); +} + +const char * +dtj_string_list_last(uu_list_t *list) +{ + dtj_string_list_entry_t *e = uu_list_last(list); + if (!e) { + /* NULL is a valid string value; use -1 for invalid */ + return (DTJ_INVALID_STR); + } + return (e->dsle_value); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtj_util.h b/usr/src/lib/libdtrace_jni/common/dtj_util.h new file mode 100644 index 0000000000..70d4c31455 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtj_util.h @@ -0,0 +1,376 @@ +/* + * 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. + */ + +#ifndef _DTJ_UTIL_H +#define _DTJ_UTIL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * dtj_util.h separates functionality that is generally useful from + * that which is specific to the Java DTrace API. If moved to a separate + * library, this functionality could be shared by other JNI wrappers. + */ + +#ifdef JNI_VERSION_1_4 +#define JNI_VERSION JNI_VERSION_1_4 +#else +#define JNI_VERSION JNI_VERSION_1_2 +#endif + +#define CONSTRUCTOR "" +#define DTJ_MSG_SIZE 1024 +#define DTJ_INVALID_PTR ((void *)-1) +#define DTJ_INVALID_STR ((const char *)-1) + +#define WRAP_EXCEPTION(JENV) dtj_wrap_exception((JENV), __FILE__, __LINE__) + +extern boolean_t g_dtj_util_debug; + +typedef enum dtj_status { + DTJ_OK = JNI_OK, + DTJ_ERR = JNI_ERR +} dtj_status_t; + +typedef enum dtj_type { + JCLASS, + JMETHOD, + JMETHOD_STATIC, + JFIELD, + JFIELD_STATIC, + DTJ_TYPE_END = -1 +} dtj_type_t; + +/* + * Convenient description format for java classes, methods, and fields. The + * java_class_t, java_method_t, and java_field_t structures derived from these + * descriptions are used to create a table of usable JNI jclass, jmethodID, and + * jfieldID instances. + */ +typedef struct dtj_table_entry { + dtj_type_t djte_type; /* JNI type */ + void *djte_addr; /* jclass, jmethodID, or jfieldID address */ + char *djte_name; /* symbol name declared in Java */ + char *djte_desc; /* JNI descriptor (string format) */ +} dtj_table_entry_t; + +typedef struct dtj_java_class { + jclass *djc_ptr; /* address in user-defined structure */ + char *djc_name; /* fully qualified '/' delimited class name */ + uu_list_t *djc_methods; /* element type (java_method_t *) */ + uu_list_t *djc_fields; /* element type (java_field_t *) */ + uu_list_node_t djc_node; +} dtj_java_class_t; + +typedef struct dtj_java_method { + jmethodID *djm_ptr; /* address in user-defined structure */ + char *djm_name; /* method name in java source file */ + char *djm_signature; /* javap -s method signature string */ + boolean_t djm_static; /* flag indicating static qualifier */ + uu_list_node_t djm_node; +} dtj_java_method_t; + +typedef struct dtj_java_field { + jfieldID *djf_ptr; /* address in user-defined structure */ + char *djf_name; /* field name in java source file */ + char *djf_type; /* javap -s field type string */ + boolean_t djf_static; /* flag indicating static qualifier */ + uu_list_node_t djf_node; +} dtj_java_field_t; + +/* + * Table of cached jclass, jmethodID, and jfieldID values usable across multiple + * native method calls and multiple threads. + * + * Suffix conventions: + * jc java class + * jm java method + * jsm java static method + * jf java field + * jsf java static field + */ + +/* NativeException */ +extern jclass g_nx_jc; +extern jmethodID g_nxinit_jm; + +/* java.io.Serializable */ +extern jclass g_serial_jc; + +/* java.lang.Number */ +extern jclass g_number_jc; +extern jmethodID g_shortval_jm; +extern jmethodID g_intval_jm; +extern jmethodID g_longval_jm; + +/* java.lang.Byte */ +extern jclass g_byte_jc; +extern jmethodID g_byteinit_jm; + +/* java.lang.Character */ +extern jclass g_char_jc; +extern jmethodID g_charinit_jm; +extern jmethodID g_charval_jm; + +/* java.lang.Short */ +extern jclass g_short_jc; +extern jmethodID g_shortinit_jm; + +/* java.lang.Integer */ +extern jclass g_int_jc; +extern jmethodID g_intinit_jm; + +/* java.lang.Long */ +extern jclass g_long_jc; +extern jmethodID g_longinit_jm; + +/* java.lang.String */ +extern jclass g_string_jc; +extern jmethodID g_strinit_bytes_jm; +extern jmethodID g_strbytes_jm; +extern jmethodID g_trim_jm; + +/* java.lang.StringBuffer */ +extern jclass g_buf_jc; +extern jmethodID g_bufinit_jm; +extern jmethodID g_buf_append_char_jm; +extern jmethodID g_buf_append_int_jm; +extern jmethodID g_buf_append_long_jm; +extern jmethodID g_buf_append_str_jm; +extern jmethodID g_buf_append_obj_jm; +extern jmethodID g_buflen_jm; +extern jmethodID g_bufsetlen_jm; + +/* java.lang.Object */ +extern jclass g_object_jc; +extern jmethodID g_tostring_jm; +extern jmethodID g_equals_jm; + +/* java.lang.Enum */ +extern jclass g_enum_jc; +extern jmethodID g_enumname_jm; + +/* List */ +extern jclass g_list_jc; +extern jmethodID g_listclear_jm; +extern jmethodID g_listadd_jm; +extern jmethodID g_listget_jm; +extern jmethodID g_listsize_jm; + +/* + * Populates the common java class references and associated method and field + * IDs declared in this file (above) using the dtj_cache_jni_classes() method. + */ +extern dtj_status_t dtj_load_common(JNIEnv *); + +/* + * Populates the user-declared java class references and associated method and + * field IDs described in the given table. Because the class references are + * created as global JNI references, the method and field IDs remain valid + * across multiple native method calls and across multiple threads. + * + * This function assumes that the given table of java class, method, and field + * descriptions is terminated by an entry with DTJ_TYPE_END, and that the + * method and field descriptions immediately follow the description of their + * containing class. + * + * Throws NoClassDefFoundError, NoSuchMethodError, or NoSuchFieldError if any + * dtj_table_entry_t in common_jni_table.c is incorrect. + */ +extern dtj_status_t dtj_cache_jni_classes(JNIEnv *, const dtj_table_entry_t *); + +/* Common utilities */ + +/* + * The following functions each create a pending Java Error or Exception: + * + * OutOfMemoryError + * NullPointerException + * IllegalArgumentException + * IllegalStateException + * NoSuchElementException + * ClassCastException + * AssertionError + * org.opensolaris.os.support.ResourceLimitException + * + * Control should be returned to Java immediately afterwards. + */ +extern void dtj_throw_out_of_memory(JNIEnv *, const char *, ...); +extern void dtj_throw_null_pointer(JNIEnv *, const char *, ...); +extern void dtj_throw_illegal_argument(JNIEnv *, const char *, ...); +extern void dtj_throw_illegal_state(JNIEnv *, const char *, ...); +extern void dtj_throw_no_such_element(JNIEnv *, const char *, ...); +extern void dtj_throw_class_cast(JNIEnv *, const char *, ...); +extern void dtj_throw_assertion(JNIEnv *, const char *, ...); +extern void dtj_throw_resource_limit(JNIEnv *, const char *, ...); + +/* + * Attaches native filename and line number to the currently pending java + * exception, since that information is not present in the exception stack + * trace. + */ +extern void dtj_wrap_exception(JNIEnv *, const char *, int); + +/* + * Calls the toString() method of the given object and prints the value to + * stdout (useful for debugging). If an exception is thrown in this function, + * it is described on stdout and cleared. It's guaranteed that no exception is + * pending when this function returns. + */ +extern void dtj_print_object(JNIEnv *jenv, jobject obj); + +/* + * Gets a formatted String (local reference) from a format and a variable + * argument list of placeholder values. Returns NULL if OutOfMemoryError is + * thrown. + */ +extern jstring dtj_format_string(JNIEnv *jenv, const char *fmt, ...); + +/* + * Internationalization support. These functions taken (not verbatim) from + * Section 8.2 of The Java Native Interface by Sheng Liang, The Java Series. + * Use these functions for locale-specific strings such as file names. + */ +extern jstring dtj_NewStringNative(JNIEnv *jenv, const char *str); +extern char *dtj_GetStringNativeChars(JNIEnv *jenv, jstring jstr); +extern void dtj_ReleaseStringNativeChars(JNIEnv *jenv, jstring jstr, + const char *str); + +/* + * Converts the args array of main(String[] args) in Java into a native + * dynamically allocated array of strings. The returned array must be + * deallocated by calling free_argv(). A java exception is pending if this + * function returns NULL (in that case, any allocations made up to the point of + * failure in get_argv() are automatically freed). + * + * Returns a NULL-terminated array that works with functions that expect a + * terminating NULL rather than relying on an element count. The argc parameter + * is also overwritten with the number of returned array elements (not including + * the terminating NULL). + */ +extern char **dtj_get_argv(JNIEnv *jenv, jobjectArray args, int *argc); +/* + * Tokenizes a command string to create a native dynamically allocated array of + * strings. The first element of the returned array is assumed to be the name + * of the command, and subsequent elements are arguments to that command. + * Otherwise behaves exactly like get_argv() above, including requiring a + * subsequent call to free_argv() on the returned array. + * Throws NullPointerException if cmd is NULL. + * Throws IllegalArgumentException if cmd is empty. + */ +extern char **dtj_make_argv(JNIEnv *jenv, jstring cmd, int *argc); +extern void dtj_free_argv(char **argv); + + +/* Wrappers for uu_list_t */ + +/* + * List element destructor. + * params: node pointer, user arg (may be NULL) + */ +typedef void dtj_value_destroy_f(void *, void *); + +/* + * uu_list_t generic entry type for pointers compared by pointer value, similar + * to Java's default Object.equals() implementation (referenced objects are + * equal only if they have the same address in memory). Used with + * pointer_list_entry_cmp. + */ +typedef struct dtj_pointer_list_entry { + void *dple_ptr; + uu_list_node_t dple_node; +} dtj_pointer_list_entry_t; + +typedef struct dtj_string_list_entry { + char *dsle_value; + uu_list_node_t dsle_node; +} dtj_string_list_entry_t; + +/* Comparison functions, uu_compare_fn_t signature */ +extern int dtj_pointer_list_entry_cmp(const void *, const void *, void *); +extern int dtj_string_list_entry_cmp(const void *, const void *, void *); + +/* Constructors */ +extern uu_list_t *dtj_pointer_list_create(void); +extern dtj_pointer_list_entry_t *dtj_pointer_list_entry_create(void *); +extern uu_list_t *dtj_string_list_create(void); +extern dtj_string_list_entry_t *dtj_string_list_entry_create(const char *); + +/* Destructors */ +extern void dtj_pointer_list_entry_destroy(void *, dtj_value_destroy_f *, + void *); +extern void dtj_string_list_entry_destroy(void *, void *); +/* + * Convenience function destroys a uu_list_t and its values. + * + * param list: list to be destroyed, call is a no-op if list is NULL + * param value_destroy: optional destructor; if non-NULL, it is called on each + * list value + * param arg: user argument to the optional destructor + */ +extern void dtj_list_destroy(uu_list_t *, dtj_value_destroy_f *, void *); +extern void dtj_pointer_list_destroy(uu_list_t *, dtj_value_destroy_f *, + void *); +extern void dtj_string_list_destroy(uu_list_t *); + +/* + * Convenience functions clear a uu_list_t without destroying it. Destroys all + * list elements and leaves the list empty. The *_list_destroy() functions + * implicitly clear the list before destroying it. + */ +extern void dtj_list_clear(uu_list_t *, dtj_value_destroy_f *, void *); +extern void dtj_pointer_list_clear(uu_list_t *, dtj_value_destroy_f *, + void *); +extern void dtj_string_list_clear(uu_list_t *); + +extern boolean_t dtj_list_empty(uu_list_t *); +/* Return B_TRUE if successful, B_FALSE otherwise */ +extern boolean_t dtj_list_add(uu_list_t *, void *); +extern boolean_t dtj_pointer_list_add(uu_list_t *, void *); +extern boolean_t dtj_string_list_add(uu_list_t *, const char *); +/* Return INVALID_PTR if list is empty (NULL is a valid list element) */ +extern void * dtj_pointer_list_first(uu_list_t *); +extern void * dtj_pointer_list_last(uu_list_t *); +/* Return INVALID_STR if list is empty (NULL is a valid list element) */ +extern const char *dtj_string_list_first(uu_list_t *); +extern const char *dtj_string_list_last(uu_list_t *); +/* Return INVALID_PTR at end of list (NULL is a valid list element) */ +extern void *dtj_pointer_list_walk_next(uu_list_walk_t *); +/* Return INVALID_STR at end of list (NULL is a valid list element) */ +extern const char *dtj_string_list_walk_next(uu_list_walk_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _DTJ_UTIL_H */ 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* generated by javah */ +#include + +/* + * 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); +} diff --git a/usr/src/lib/libdtrace_jni/common/dtrace_jni.h b/usr/src/lib/libdtrace_jni/common/dtrace_jni.h new file mode 100644 index 0000000000..7df12ce930 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/common/dtrace_jni.h @@ -0,0 +1,491 @@ +/* + * 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. + */ + +#ifndef _DTRACE_JNI_H +#define _DTRACE_JNI_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Java DTrace API native library */ + + +/* + * Thread-specific data key used to obtain JNI state specific to either the + * consumer loop (calls dtrace_work()) or the getAggregate() method (calls + * dtrace_aggregate_print()). + */ +extern pthread_key_t g_dtj_consumer_key; + +typedef enum dtj_consumer_state { + DTJ_CONSUMER_INIT, + DTJ_CONSUMER_GO, + DTJ_CONSUMER_START, + DTJ_CONSUMER_STOP +} dtj_consumer_state_t; + +typedef struct dtj_error { + int dtje_number; /* dtrace_errno() */ + const char *dtje_message; /* dtrace_errmsg() */ +} dtj_error_t; + +/* + * Identifies which function should handle a request dequeued after + * dtrace_sleep(). + */ +typedef enum dtj_request_type { + DTJ_REQUEST_OPTION /* set DTrace runtime option */ +} dtj_request_type_t; + +/* + * A request made from Java (by native method call) that is unsafe to process + * until just after the consumer loop wakes up from dtrace_sleep(). + */ +typedef struct dtj_request { + dtj_request_type_t dtjr_type; /* request handler ID */ + uu_list_t *dtjr_args; /* string args to request handler */ + uu_list_node_t dtjr_node; /* points to next and prev requests */ +} dtj_request_t; + +typedef enum dtj_program_type { + DTJ_PROGRAM_NONE, + DTJ_PROGRAM_STRING, /* dtrace_program_strcompile() */ + DTJ_PROGRAM_FILE /* dtrace_program_fcompile() */ +} dtj_program_type_t; + +/* Identifier and description of a compiled DTrace program */ +typedef struct dtj_program { + dtj_program_type_t dtjp_type; /* string or file */ + const char *dtjp_name; /* string or filename for err msg */ + dtrace_prog_t *dtjp_program; /* libdtrace program handle */ + dtrace_proginfo_t dtjp_info; /* program attributes */ + boolean_t dtjp_enabled; /* dtrace_program_exec() flag */ + uu_list_node_t dtjp_node; /* points to next and prev programs */ +} dtj_program_t; + +/* + * An entry used to maintain the association between the value of an aggregating + * action (such as count()) and the aggregation to which the value belongs until + * all the data associated with a single tuple is available to the callback + * handler. + */ +typedef struct dtj_aggval { + jobject dtja_value; /* value of aggregating action */ + const char *dtja_aggname; /* aggregation name */ + int64_t dtja_aggid; /* libdtrace aggregation ID */ + uu_list_node_t dtja_node; /* points to next and prev aggvals */ +} dtj_aggval_t; + +/* + * Per-consumer state, including the libdtrace consumer handle, is valid across + * multiple threads. One consumer entry is added to a global table per + * dtrace_open(). + */ +typedef struct dtj_consumer { + /* Consumer state */ + + dtrace_hdl_t *dtjc_dtp; /* libdtrace consumer handle */ + uu_list_t *dtjc_program_list; /* program_t list */ + uu_list_t *dtjc_process_list; /* proc handle list */ + + /* + * Count of processes that have ended. The consumer is stopped when + * this count equals the number of outstanding target processes and + * grabbed processes (see the Java Consumer createProcess() and + * grabProcess() methods). + */ + int dtjc_procs_ended; + + /* + * Bit-field passed to dtrace_program_strcompile() and + * dtrace_program_fcompile() containing compile flags. The flags are + * set from Java by the setOption() Consumer method (just like the + * runtime options handled by dtrace_setopt(), except that they must be + * set before program compilation to have any effect). + */ + uint_t dtjc_cflags; + + boolean_t dtjc_flow; /* current value of the flowindent option */ + dtj_consumer_state_t dtjc_state; /* execution state */ + boolean_t dtjc_interrupt; /* flag that stops consumer */ + + /* Pending requests */ + uu_list_t *dtjc_request_list; /* request_t queue */ + pthread_mutex_t dtjc_request_list_lock; + + + /* Cached for optimization and for use across functions */ + + /* + * Nanosecond timestamp cached in the consumer loop just before + * dtrace_work(). The timestamp is applied to each Java PrintaRecord + * generated in that iteration of the consumer loop. A value of zero + * indicates that we are not in the consumer loop, but that the + * callback was triggered instead by the Consumer getAggregate() method + * (from dtrace_aggregate_print()). + */ + hrtime_t dtjc_printa_snaptime; + + /* + * The aggregation ID is used to optimize aggregation inclusion by + * testing for inclusion only when the aggregation has changed. + */ + int64_t dtjc_aggid; + boolean_t dtjc_included; + + /* + * The expected tuple member count is used to determine whether or not + * the aggregation tuple values are completely specified in the printa() + * format string. + */ + int dtjc_expected; + + int dtjc_probedata_rec_i; /* probe data record index */ + + /* + * The current DTrace action may apply across multiple libdtrace probe + * data records. + */ + dtrace_actkind_t dtjc_probedata_act; + + /* Placeholder used when listing probes */ + dtrace_ecbdesc_t *dtjc_last_probe; + + /* Function used by statement iterator when listing probes */ + dtrace_probe_f *dtjc_plistfunc; +} dtj_consumer_t; + +/* + * A view of a dtj_consumer_t that lasts only as long as a single native method + * call. This view attaches state needed for interaction with Java and specific + * to the JNI. + */ +typedef struct dtj_java_consumer { + /* Per-consumer state in global consumer table */ + dtj_consumer_t *dtjj_consumer; + + JNIEnv *dtjj_jenv; /* Java environment pointer */ + jobject dtjj_caller; /* Java Consumer to call back with probe data */ + + /* + * Java Object references used across function boundaries, valid only + * within the current native method call. + */ + + jobject dtjj_probedata; /* instance of class ProbeData */ + + /* + * StringBuffer used to concatenate buffered printa() output associated + * with the current tuple. + */ + jobject dtjj_printa_buffer; + + jobject dtjj_aggregate; /* instance of class Aggregate */ + jobject dtjj_tuple; /* instance of class Tuple */ + + /* + * AggregationValue instances cached until we receive the + * DTRACE_BUFDATA_AGGLAST flag indicating the last callback associated + * with the current tuple. + */ + uu_list_t *dtjj_aggval_list; + + /* AggregateSpec used by get_aggregate() */ + jobject dtjj_aggregate_spec; + + jobject dtjj_probelist; /* java.util.List returned by listProbes() */ + + /* + * Exception temporarily cleared by callback handlers who cannot return + * a signal to abort the consumer. At a safe point when the consumer + * loop gets control back from libdtrace, the exception is rethrown. + */ + jthrowable dtjj_exception; + + jobject dtjj_consumer_lock; /* per-consumer lock */ + +} dtj_java_consumer_t; + +/* + * Cache of jclass, jmethodID, and jfieldID values, usable across multiple + * native method calls and multiple threads. Caching all of them up front + * rather than as-needed guarantees early detection of incorrect class, method, + * or field definitions, and eliminates the need for test cases to cover + * seldom-used definitions. + * + * Suffix conventions: + * jc java class + * jm java method + * jsm java static method + * jf java field + * jsf java static field + */ + +/* LocalConsumer */ +extern jclass g_caller_jc; +extern jmethodID g_gethandle_jm; +extern jmethodID g_sethandle_jm; +extern jmethodID g_pdatanext_jm; +extern jmethodID g_drop_jm; +extern jmethodID g_error_jm; +extern jmethodID g_proc_jm; +extern jmethodID g_interval_began_jm; +extern jmethodID g_interval_ended_jm; +extern jfieldID g_consumer_lock_jf; + +/* DTraceException */ +extern jclass g_dtx_jc; +extern jmethodID g_dtxinit_jm; + +/* InterfaceAttributes */ +extern jclass g_attr_jc; +extern jmethodID g_attrinit_jm; +extern jmethodID g_attrset_name_jm; +extern jmethodID g_attrset_data_jm; +extern jmethodID g_attrset_class_jm; + +/* ProbeDescription */ +extern jclass g_probedesc_jc; +extern jmethodID g_probedescinit_jm; +extern jfieldID g_probedesc_id_jf; + +/* ProbeInfo */ +extern jclass g_probeinfo_jc; +extern jmethodID g_probeinfoinit_jm; + +/* Probe */ +extern jclass g_probe_jc; +extern jmethodID g_probeinit_jm; + +/* Program */ +extern jclass g_program_jc; +extern jmethodID g_proginit_jm; +extern jfieldID g_progid_jf; +extern jfieldID g_proginfo_jf; + +/* Program.File */ +extern jclass g_programfile_jc; +extern jmethodID g_fproginit_jm; + +/* ProgramInfo */ +extern jclass g_proginfo_jc; +extern jmethodID g_proginfoinit_jm; + +/* Flow */ +extern jclass g_flow_jc; +extern jmethodID g_flowinit_jm; + +/* ProbeData */ +extern jclass g_pdata_jc; +extern jmethodID g_pdatainit_jm; +extern jmethodID g_pdataadd_jm; +extern jmethodID g_pdataadd_rec_jm; +extern jmethodID g_pdataadd_trace_jm; +extern jmethodID g_pdataadd_stack_jm; +extern jmethodID g_pdataadd_printf_jm; +extern jmethodID g_pdataadd_printa_jm; +extern jmethodID g_pdatainvalidate_printa_jm; +extern jmethodID g_pdataadd_aggrec_jm; +extern jmethodID g_pdataadd_printa_str_jm; +extern jmethodID g_pdataadd_exit_jm; +extern jmethodID g_pdataattach_jm; +extern jmethodID g_pdataset_formatted_jm; +extern jmethodID g_pdataclear_jm; + +/* Drop */ +extern jclass g_drop_jc; +extern jmethodID g_dropinit_jm; + +/* Error */ +extern jclass g_error_jc; +extern jmethodID g_errinit_jm; + +/* ProcessState */ +extern jclass g_process_jc; +extern jmethodID g_procinit_jm; +extern jmethodID g_procexit_jm; + +/* Aggregate */ +extern jclass g_agg_jc; +extern jmethodID g_agginit_jm; +extern jmethodID g_aggaddrec_jm; + +/* AggregateSpec */ +extern jclass g_aggspec_jc; +extern jmethodID g_aggspec_included_jm; +extern jmethodID g_aggspec_cleared_jm; + +/* Tuple */ +extern jclass g_tuple_jc; +extern jmethodID g_tupleinit_jm; +extern jmethodID g_tupleadd_jm; +extern jmethodID g_tuplesize_jm; +extern jfieldID g_tuple_EMPTY_jsf; + +/* AggregationRecord */ +extern jclass g_aggrec_jc; +extern jmethodID g_aggrecinit_jm; +extern jmethodID g_aggrecget_tuple_jm; + +/* SumValue */ +extern jclass g_aggsum_jc; +extern jmethodID g_aggsuminit_jm; + +/* CountValue */ +extern jclass g_aggcount_jc; +extern jmethodID g_aggcountinit_jm; + +/* AvgValue */ +extern jclass g_aggavg_jc; +extern jmethodID g_aggavginit_jm; + +/* MinValue */ +extern jclass g_aggmin_jc; +extern jmethodID g_aggmininit_jm; + +/* MaxValue */ +extern jclass g_aggmax_jc; +extern jmethodID g_aggmaxinit_jm; + +/* KernelStackRecord */ +extern jclass g_stack_jc; +extern jmethodID g_parsestack_jsm; +extern jmethodID g_stackinit_jm; +extern jmethodID g_stackset_frames_jm; + +/* UserStackRecord */ +extern jclass g_ustack_jc; +extern jmethodID g_ustackinit_jm; +extern jmethodID g_ustackset_frames_jm; + +/* Distribution */ +extern jclass g_adist_jc; +extern jmethodID g_dist_normal_jm; + +/* LogDistribution */ +extern jclass g_dist_jc; +extern jmethodID g_distinit_jm; + +/* LinearDistribution */ +extern jclass g_ldist_jc; +extern jmethodID g_ldistinit_jm; + +/* + * Populates the java class references and associated method and field IDs + * declared in this file (above). + * + * Throws NoClassDefFoundError, NoSuchMethodError, or NoSuchFieldError if any + * dtj_table_entry_t in dtj_jnitab.c is incorrect. + */ +extern dtj_status_t dtj_load(JNIEnv *); + +/* + * Functions that create a structure return NULL if out of memory. A Java + * OutOfMemoryError is pending in that case. + */ +extern dtj_request_t *dtj_request_create(JNIEnv *, dtj_request_type_t, ...); +extern dtj_program_t *dtj_program_create(JNIEnv *, dtj_program_type_t, + const char *); +extern dtj_aggval_t *dtj_aggval_create(JNIEnv *, jobject, const char *, + int64_t); + +/* + * uu_list_t element destructors' signatures match uuwrap_value_destroy_f + */ +extern void dtj_request_destroy(void *, void *); /* expects NULL user arg */ +extern void dtj_program_destroy(void *, void *); /* expects NULL user arg */ +extern void dtj_aggval_destroy(void *, void *); /* expects JNIEnv * user arg */ + +/* Allocates and frees per-consumer state kept in the global consumer table */ +extern dtj_consumer_t *dtj_consumer_create(JNIEnv *); +extern void dtj_consumer_destroy(dtj_consumer_t *); + +/* Sets callback handlers before calling dtrace_go() */ +extern dtj_status_t dtj_set_callback_handlers(dtj_java_consumer_t *); + +/* + * Initializes Java Object references cached across multiple functions called + * within the consumer loop. Deletes the references after exiting the consumer + * loop. It is only necessary to initialize and finalize a dtj_java_consumer_t + * if the native method call will enter the consumer loop. + */ +extern dtj_status_t dtj_java_consumer_init(JNIEnv *, dtj_java_consumer_t *); +extern void dtj_java_consumer_fini(JNIEnv *, dtj_java_consumer_t *); + +/* + * Throws a DTraceException with a message constructed from the given format + * string and variable arg list. + */ +extern void dtj_throw_dtrace_exception(dtj_java_consumer_t *, + const char *, ...); + +/* Returns NULL if pending Java Exception or OutOfMemoryError */ +extern jobject dtj_new_probedesc(dtj_java_consumer_t *, + const dtrace_probedesc_t *); +extern jobject dtj_new_probeinfo(dtj_java_consumer_t *, + const dtrace_probeinfo_t *); +extern jobject dtj_new_attribute(dtj_java_consumer_t *, + const dtrace_attribute_t *); + +/* + * Returns NULL if the given fault is unrecognized, otherwise returns the name + * of the fault, guaranteed not to change across multiple versions of this API + * even if the integer value changes in libdtrace. + */ +extern const char *dtj_get_fault_name(int); + +/* Gets the libdtrace error number and message */ +extern dtj_status_t dtj_get_dtrace_error(dtj_java_consumer_t *, dtj_error_t *); + +/* Stops the DTrace consumer */ +extern void dtj_stop(dtj_java_consumer_t *); + +/* + * The Consumer getAggregate() method runs in the caller's current thread + * separate from the consumer loop. + */ +extern jobject dtj_get_aggregate(dtj_java_consumer_t *); + +/* + * A blocking call that runs the consumer loop. If this function returns an + * error status, it is necessary to call stop() in order to dtrace_stop() the + * consumer in libdtrace (it is safe to call stop() in either case). + */ +extern dtj_status_t dtj_consume(dtj_java_consumer_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _DTRACE_JNI_H */ diff --git a/usr/src/lib/libdtrace_jni/i386/Makefile b/usr/src/lib/libdtrace_jni/i386/Makefile new file mode 100644 index 0000000000..5f95ecb5e8 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/i386/Makefile @@ -0,0 +1,33 @@ +# +# 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. +# +# ident "%Z%%M% %I% %E% SMI" +# + +MAPDIR = ../spec/i386 + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libdtrace_jni/java/Makefile b/usr/src/lib/libdtrace_jni/java/Makefile new file mode 100644 index 0000000000..b47b8ea6f9 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/java/Makefile @@ -0,0 +1,242 @@ +# +# 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. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include $(SRC)/Makefile.master + +# The Java DTrace API package + +PKGPATH=org/opensolaris/os/dtrace +PKGNAME=org.opensolaris.os.dtrace + +# The Java DTrace API directories + +APIDIR=$(SRC)/lib/libdtrace_jni/java +CLASSES=$(APIDIR)/classes +JNI=$(APIDIR)/native +JARS=$(APIDIR)/lib +DOCS=$(APIDIR)/docs +DOCAPI=$(DOCS)/api +DOCEXAMPLES=$(DOCS)/examples +DOCHTML=$(DOCS)/html +DOCIMAGES=$(DOCS)/images +MANIFEST=$(APIDIR)/manifest +APIJAVASRC=$(APIDIR)/src + +# Java DTrace API jar file, its ultimate destination, and packaging details +APIJAR=dtrace.jar +JARDESTDIR=$(ROOT)/usr/share/lib/java +INSJARS=$(JARDESTDIR)/$(APIJAR) +# javadoc +DOCDESTDIR=$(ROOT)/usr/share/lib/java/javadoc/dtrace +DOCAPIDESTDIR=$(DOCDESTDIR)/api +DOCEXAMPLESDESTDIR=$(DOCDESTDIR)/examples +DOCHTMLDESTDIR=$(DOCDESTDIR)/html +DOCIMAGESDESTDIR=$(DOCDESTDIR)/images + +$(JARDESTDIR)/%: $(JARS)/% + $(INS.file) + +$(DOCEXAMPLESDESTDIR)/%: $(DOCEXAMPLES)/% + $(INS.file) + +$(DOCHTMLDESTDIR)/%: $(DOCHTML)/% + $(INS.file) + +$(DOCIMAGESDESTDIR)/%: $(DOCIMAGES)/% + $(INS.file) + +# Manifest files +APIMANI= $(MANIFEST)/dtrace.mf +MANIFESTS= $(APIMANI) + +# Controlled CLASSPATH for making +APICLASSPATH=$(CLASSES):$(APIJAVASRC) + +# javac flags +JFLAGS= -g -d $(CLASSES) -sourcepath $(APICLASSPATH) -deprecation + +# The default make rule for Java files +COMPILE.java=$(JAVAC) $(JFLAGS) + +$(CLASSES)/$(PKGPATH)/%.class: $(APIJAVASRC)/$(PKGPATH)/%.java + $(COMPILE.java) $< + +# javah generated headers +APIHDR= LocalConsumer.h +JNIHDRS=$(JNI)/$(APIHDRS) + +# API classes with native methods +JNI_CLASSNAMES=\ + LocalConsumer + +JNI_CLASSES=${JNI_CLASSNAMES:%=%.class} +DTRACE_JNI_CLASSES=${JNI_CLASSES:%=$(CLASSES)/$(PKGPATH)/%} +JNI_FULL_CLASSNAMES=${JNI_CLASSNAMES:%=$(PKGNAME).%} + +# All API classes +API_CLASSNAMES=\ + AbstractAggregationValue \ + Aggregate \ + AggregateSpec \ + Aggregation \ + AggregationRecord \ + AggregationValue \ + AvgValue \ + Consumer \ + ConsumerAdapter \ + ConsumerEvent \ + ConsumerException \ + ConsumerListener \ + CountValue \ + DTraceException \ + DataEvent \ + Distribution \ + Drop \ + DropEvent \ + Error \ + ErrorEvent \ + ExceptionHandler \ + ExitRecord \ + Flow \ + InterfaceAttributes \ + KernelStackRecord \ + LinearDistribution \ + LocalConsumer \ + LogDistribution \ + MaxValue \ + MinValue \ + NativeException \ + Option \ + PrintaRecord \ + PrintfRecord \ + Probe \ + ProbeData \ + ProbeDescription \ + ProbeInfo \ + ProcessEvent \ + ProcessState \ + Program \ + ProgramInfo \ + Record \ + ResourceLimitException \ + ScalarRecord \ + StackFrame \ + StackValueRecord \ + SumValue \ + Tuple \ + UserStackRecord \ + Utility \ + ValueRecord + +API_CLASSES=${API_CLASSNAMES:%=%.class} +DTRACE_API_CLASSES=${API_CLASSES:%=$(CLASSES)/$(PKGPATH)/%} + + +all: $(CLASSES) $(DTRACE_API_CLASSES) $(JNI)/$(APIHDR) + +clean: + -$(RM) $(CLASSES)/$(PKGPATH)/*.class + -$(RM) $(JNI)/*.h + -$(RM) -r $(DOCAPI) + +clobber: clean + -$(RM) $(JARS)/*.jar + +# Make the class dir, if it doesn't exist +$(CLASSES): + -@mkdir -p $@ + +# Make the directory for javah-generated headers, if it doesn't exist +$(JNI): + -@mkdir -p $@ + +$(JNI)/$(APIHDR): $(JNI) $(DTRACE_JNI_CLASSES) + $(JAVAH) -o $@ -classpath $(CLASSES) $(JNI_FULL_CLASSNAMES) + -@touch $@ + +# Rule for installing API javadoc. +$(DOCAPIDESTDIR)/index.html: $(DTRACE_API_CLASSES) + -@mkdir -p $(DOCAPIDESTDIR) + -$(RM) -r $(DOCAPIDESTDIR)/* + cd $(APIJAVASRC); $(JAVADOC) -protected -use \ + -classpath $(APICLASSPATH) -d $(DOCAPIDESTDIR) \ + $(PKGNAME) + +$(CLASSES)/$(PKGPATH): + $(INS.dir) + +$(JARS)/$(APIJAR): $(DTRACE_API_CLASSES) $(APIMANI) + -@mkdir -p $(JARS) + $(JAR) cfm $@ $(APIMANI) -C $(CLASSES) . + +$(JARDESTDIR): + $(INS.dir) + +$(DOCDESTDIR): + $(INS.dir) + +$(DOCAPIDESTDIR): + $(INS.dir) + +$(DOCEXAMPLESDESTDIR): + $(INS.dir) + +$(DOCHTMLDESTDIR): + $(INS.dir) + +$(DOCIMAGESDESTDIR): + $(INS.dir) + +install: all $(JARDESTDIR) $(INSJARS) $(DOCDESTDIR) \ + $(DOCAPIDESTDIR) \ + $(DOCAPIDESTDIR)/index.html \ + $(DOCEXAMPLESDESTDIR) \ + $(DOCEXAMPLESDESTDIR)/TestAPI.java \ + $(DOCEXAMPLESDESTDIR)/TestAPI2.java \ + $(DOCEXAMPLESDESTDIR)/TestTarget.java \ + $(DOCEXAMPLESDESTDIR)/hello.d \ + $(DOCEXAMPLESDESTDIR)/intrstat.d \ + $(DOCEXAMPLESDESTDIR)/syscall.d \ + $(DOCEXAMPLESDESTDIR)/target.d \ + $(DOCHTMLDESTDIR) \ + $(DOCHTMLDESTDIR)/JavaDTraceAPI.html \ + $(DOCHTMLDESTDIR)/fast.html \ + $(DOCIMAGESDESTDIR) \ + $(DOCIMAGESDESTDIR)/JavaDTraceAPI.gif + +# empty targets for top-level building compatability + +install_h lint: + +# create API javadoc + +doc: + -@mkdir -p $(DOCAPI) + cd $(APIJAVASRC); $(JAVADOC) -protected -use \ + -classpath $(APICLASSPATH) -d $(DOCAPI) \ + $(PKGNAME) diff --git a/usr/src/lib/libdtrace_jni/java/docs/examples/TestAPI.java b/usr/src/lib/libdtrace_jni/java/docs/examples/TestAPI.java new file mode 100644 index 0000000000..bd7fad2aa8 --- /dev/null +++ b/usr/src/lib/libdtrace_jni/java/docs/examples/TestAPI.java @@ -0,0 +1,62 @@ +/* + * 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. + * + * ident "%Z%%M% %I% %E% SMI" + */ + +import org.opensolaris.os.dtrace.*; +import java.io.File; + +public class TestAPI { + public static void + main(String[] args) + { + if (args.length < 1) { + System.err.println("Usage: java TestAPI