github.com/luchsh/agentlib.go@v0.0.0-20221115155834-ffd0caec4d72/jgo/java.go (about)

     1  //
     2  // Copyright 2022 chuanshenglu@gmail.com
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  // http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  // The goal is to decouple the exposed interface from CGO, JNI or JVMTI
    18  
    19  package jgo
    20  
    21  //#include<jni.h>
    22  //#include<stdlib.h>
    23  //#include<jvmti.h>
    24  import "C"
    25  
    26  import (
    27  	"fmt"
    28  	"runtime"
    29  	"strings"
    30  	"unsafe"
    31  
    32  	. "github.com/ClarkGuan/jni"
    33  )
    34  
    35  type JClass uintptr
    36  type JObject uintptr
    37  
    38  // Unified interface for both jni and jvmti
    39  type JavaVM struct {
    40  	jvmti jvmtiEnv
    41  	jni   Env
    42  	jvm   VM
    43  }
    44  
    45  // The JavaVM instance for main thread
    46  // TODO: maybe we should remove curVM
    47  var theVM *JavaVM
    48  
    49  // create and launch a new Java virtual machine
    50  // with current thread attached as the main thread
    51  func Exec(args []string) (*JavaVM, error) {
    52  	if theVM != nil {
    53  		return nil, fmt.Errorf("Cannot create multiple JVM in the same process")
    54  	}
    55  	jvm, jni := jniCreateJavaVM(args)
    56  	if jvm == 0 || jni == 0 {
    57  		return nil, fmt.Errorf("Failed to create JavaVM with args: %s", strings.Join(args, " "))
    58  	}
    59  	jvmti, err := jvm.GetEnv(JVMTI_VERSION_1_1)
    60  	if err != JNI_OK {
    61  		return nil, fmt.Errorf("GetEnv error=%d", err)
    62  	}
    63  
    64  	theVM = &JavaVM{
    65  		jni:   Env(jni),
    66  		jvm:   VM(jvm),
    67  		jvmti: jvmtiEnv(jvmti),
    68  	}
    69  	return theVM, nil
    70  }
    71  
    72  // create a usable *JavaVM instance from raw jni.VM
    73  func createJavaVM(vm VM) (*JavaVM, error) {
    74  	if vm == 0 {
    75  		return nil, fmt.Errorf("NULL VM")
    76  	}
    77  	env, i := vm.AttachCurrentThread()
    78  	if i != JNI_OK {
    79  		return nil, fmt.Errorf("Cannot attach current thread, error=%s", describeJNIError(i))
    80  	}
    81  	je, e := vm.GetEnv(JVMTI_VERSION_1_1)
    82  	if e != JNI_OK {
    83  		return nil, fmt.Errorf("Cannot get JVMTI env, error=%s", describeJNIError(e))
    84  	}
    85  	return &JavaVM{
    86  		jni:   env,
    87  		jvmti: jvmtiEnv(je),
    88  		jvm:   vm,
    89  	}, nil
    90  }
    91  
    92  // Get the context VM for a goroutine to use
    93  // must be called after setting up the global unique VM instance
    94  // e.g. calling Exec(...)
    95  func contextVM() (*JavaVM, error) {
    96  	if theVM == nil {
    97  		return nil, fmt.Errorf("JavaVM instance not found for this process")
    98  	}
    99  	return createJavaVM(theVM.jvm)
   100  }
   101  
   102  // Retrieve the unique VM instance
   103  // Current spec only allows one JVM in each process
   104  func CurrentVM() *JavaVM {
   105  	v, e := contextVM()
   106  	if e != nil {
   107  		panic(e)
   108  	}
   109  	env, _ := v.jvm.AttachCurrentThread()
   110  	return &JavaVM{
   111  		jni:   env,
   112  		jvm:   v.jvm,
   113  		jvmti: v.jvmti,
   114  	}
   115  }
   116  
   117  func (jvm *JavaVM) FullVersion() string {
   118  	iv := int(jvm.jni.GetVersion())
   119  	primVer := (iv & 0xFFFF0000) >> 16
   120  	minorVer := (iv & 0xFFFF)
   121  	return fmt.Sprintf("Java version %d.%d", primVer, minorVer)
   122  }
   123  
   124  // Retrieve all the properties from the Java VM
   125  func (jvm *JavaVM) GetSystemProperties() (map[string]string, error) {
   126  	runtime.LockOSThread()
   127  	defer runtime.UnlockOSThread()
   128  
   129  	var n C.jint
   130  	var p **C.char
   131  	if e := jvm.jvmti.getSystemProperties(&n, &p); e != JVMTI_ERROR_NONE {
   132  		return nil, fmt.Errorf("Failed to get system properties: %s", describeJvmtiError(int(e)))
   133  	}
   134  	if n <= 0 {
   135  		return nil, fmt.Errorf("no properties found!")
   136  	}
   137  
   138  	defer jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(p)))
   139  
   140  	res := make(map[string]string)
   141  	for i := 0; i < int(n); i++ {
   142  		addr := uintptr(unsafe.Pointer(p)) + uintptr(i)*ptrSize
   143  		ks := *(**C.char)(unsafe.Pointer(addr))
   144  		var vs *C.char
   145  		if e := jvm.jvmti.getSystemProperty(ks, &vs); e != JVMTI_ERROR_NONE {
   146  			return nil, fmt.Errorf("failed get prop %s, error=%s", C.GoString(ks), describeJvmtiError(int(e)))
   147  		}
   148  		defer jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(vs)))
   149  		res[C.GoString(ks)] = C.GoString(vs)
   150  	}
   151  	return res, nil
   152  }
   153  
   154  // Retrieve property value of given key
   155  // returns "" if not found or error
   156  func (jvm *JavaVM) GetSystemProperty(key string) string {
   157  	var vs *C.char
   158  	ks := C.CString(key)
   159  	defer C.free(unsafe.Pointer(ks))
   160  	if e := jvm.jvmti.getSystemProperty(ks, &vs); e != JVMTI_ERROR_NONE {
   161  		return ""
   162  	}
   163  	defer jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(vs)))
   164  	return C.GoString(vs)
   165  }
   166  
   167  // Set the value of target system property
   168  // only allowed to be called at agent OnLoad phase
   169  // According to https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#GetSystemProperty
   170  func (jvm *JavaVM) SetSystemProperty(key, value string) error {
   171  	ck := C.CString(key)
   172  	cv := C.CString(value)
   173  	defer func() {
   174  		C.free(unsafe.Pointer(ck))
   175  		C.free(unsafe.Pointer(cv))
   176  	}()
   177  	if e := jvm.jvmti.setSystemProperty(ck, cv); e != JVMTI_ERROR_NONE {
   178  		return fmt.Errorf("Failed to set property, error=%s\n", describeJvmtiError(int(e)))
   179  	}
   180  	return nil
   181  }
   182  
   183  type Frame struct {
   184  	PC     uintptr
   185  	Func   string
   186  	Source string
   187  }
   188  
   189  func (f *Frame) String() string {
   190  	return fmt.Sprintf("%s@%p(%s)", f.Func, unsafe.Pointer(f.PC), f.Source)
   191  }
   192  
   193  type Thread struct {
   194  	jt          C.jobject
   195  	state       int
   196  	IsDaemon    bool
   197  	Name        string
   198  	StackTraces []Frame
   199  }
   200  
   201  func (t *Thread) String() string {
   202  	var sb strings.Builder
   203  	sb.WriteString(fmt.Sprintf("Thread@%p: %s, is_daemon=%v\n", unsafe.Pointer(t.jt), t.Name, t.IsDaemon))
   204  	for i, fr := range t.StackTraces {
   205  		sb.WriteString(fmt.Sprintf("\t[%d] %s\n", i, fr.String()))
   206  	}
   207  	return sb.String()
   208  }
   209  
   210  func (jvm *JavaVM) fillThread(jt C.jobject) (rt *Thread, err error) {
   211  	rt = &Thread{
   212  		jt: jt,
   213  	}
   214  	ti := &C.struct__jvmtiThreadInfo{}
   215  	if e := jvm.jvmti.getThreadInfo(rt.jt, ti); e != JVMTI_ERROR_NONE {
   216  		return nil, fmt.Errorf("JVMTI GetThreadInfo returns %s", describeJvmtiError(int(e)))
   217  	}
   218  	rt.IsDaemon = (ti.is_daemon == C.JNI_TRUE)
   219  	rt.Name = C.GoString(ti.name)
   220  
   221  	var tst C.jint
   222  	if e := jvm.jvmti.getThreadState(rt.jt, &tst); e != JVMTI_ERROR_NONE {
   223  		return nil, fmt.Errorf("JVMTI GetThreadState returns %s", describeJvmtiError(int(e)))
   224  	}
   225  	rt.state = int(tst)
   226  	rt.StackTraces, err = jvm.stackTraceOf(jt)
   227  	return
   228  }
   229  
   230  func (jvm *JavaVM) stackTraceOf(jt C.jobject) (frames []Frame, err error) {
   231  	var nfrs C.jint
   232  	var jfrs *C.struct__jvmtiFrameInfo
   233  	flmt := C.jint(1024)
   234  	if e := jvm.jvmti.allocate(C.jlong(C.sizeof_struct__jvmtiFrameInfo)*C.jlong(flmt), (**C.uchar)(unsafe.Pointer(&jfrs))); e != JVMTI_ERROR_NONE {
   235  		defer jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(jfrs)))
   236  	}
   237  	if e := jvm.jvmti.getStackTrace(jt, C.jint(0), flmt, jfrs, &nfrs); e != JVMTI_ERROR_NONE {
   238  		return nil, fmt.Errorf("JVMTI GetStackTrace failed with %s", describeJvmtiError(int(e)))
   239  	}
   240  	for i := 0; i < int(nfrs); i++ {
   241  		p := (*C.struct__jvmtiFrameInfo)(unsafe.Pointer((uintptr(unsafe.Pointer(jfrs)) + uintptr(C.sizeof_struct__jvmtiFrameInfo)*uintptr(i))))
   242  		//jloc := p.location
   243  		var name *C.char
   244  		var sig *C.char
   245  		var gen *C.char
   246  		if e := jvm.jvmti.getMethodName(p.method, &name, &sig, &gen); e != JVMTI_ERROR_NONE {
   247  			return nil, fmt.Errorf("JVMTI GetMethodName failed with %s", describeJvmtiError(int(e)))
   248  		}
   249  		defer func() {
   250  			jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(name)))
   251  			jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(sig)))
   252  			jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(gen)))
   253  		}()
   254  
   255  		fr := Frame{
   256  			PC:   uintptr(p.location),
   257  			Func: fmt.Sprintf("%s:%s:%s", C.GoString(name), C.GoString(sig), C.GoString(gen)),
   258  		}
   259  		frames = append(frames, fr)
   260  	}
   261  	return
   262  }
   263  
   264  func (jvm *JavaVM) CurrentThread() (rt *Thread, err error) {
   265  	var jt C.jobject
   266  	if e := jvm.jvmti.getCurrentThread(&jt); e != JVMTI_ERROR_NONE {
   267  		return nil, fmt.Errorf("JVMTI GetCurrentThread returns %s", describeJvmtiError(int(e)))
   268  	}
   269  	return jvm.fillThread(jt)
   270  }
   271  
   272  func (jvm *JavaVM) DumpThreads() (thrds []*Thread, err error) {
   273  	var jts *C.jobject
   274  	var nt C.jint
   275  	if e := jvm.jvmti.getAllThreads(&nt, &jts); e != JVMTI_ERROR_NONE {
   276  		return nil, fmt.Errorf("JVMTI GetAllThreads returns %s", describeJvmtiError(int(e)))
   277  	}
   278  	defer jvm.jvmti.deallocate((*C.uchar)(unsafe.Pointer(jts)))
   279  	for i := 0; i < int(nt); i++ {
   280  		p := (*C.jobject)(unsafe.Pointer(uintptr(unsafe.Pointer(jts)) + uintptr(i)*ptrSize))
   281  		if t, e := jvm.fillThread(*p); e != nil {
   282  			return nil, e
   283  		} else {
   284  			thrds = append(thrds, t)
   285  		}
   286  	}
   287  	return
   288  }