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 }