| 1 | /* |
| 2 | * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | * accompanied this code). |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License version |
| 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | * |
| 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 20 | * or visit www.oracle.com if you need additional information or have any |
| 21 | * questions. |
| 22 | * |
| 23 | */ |
| 24 | |
| 25 | #include "sun_jvm_hotspot_asm_Disassembler.h" |
| 26 | |
| 27 | /* |
| 28 | * This file implements a binding between Java and the hsdis |
| 29 | * disassembler. It should compile on Linux/Solaris and Windows. |
| 30 | * The only platform dependent pieces of the code for doing |
| 31 | * dlopen/dlsym to find the entry point in hsdis. All the rest is |
| 32 | * standard JNI code. |
| 33 | */ |
| 34 | |
| 35 | #ifdef _WINDOWS |
| 36 | // Disable CRT security warning against _snprintf |
| 37 | #pragma warning (disable : 4996) |
| 38 | |
| 39 | #define snprintf _snprintf |
| 40 | #define vsnprintf _vsnprintf |
| 41 | |
| 42 | #include <windows.h> |
| 43 | #include <sys/types.h> |
| 44 | #include <sys/stat.h> |
| 45 | #ifdef _DEBUG |
| 46 | #include <crtdbg.h> |
| 47 | #endif |
| 48 | |
| 49 | #else |
| 50 | |
| 51 | #include <string.h> |
| 52 | #include <dlfcn.h> |
| 53 | |
| 54 | #ifndef __APPLE__ |
| 55 | #include <link.h> |
| 56 | #endif |
| 57 | |
| 58 | #endif |
| 59 | |
| 60 | #include <limits.h> |
| 61 | #include <stdio.h> |
| 62 | #include <stdarg.h> |
| 63 | #include <stdlib.h> |
| 64 | #include <errno.h> |
| 65 | |
| 66 | #ifdef _WINDOWS |
| 67 | #define JVM_MAXPATHLEN _MAX_PATH |
| 68 | #else |
| 69 | #include <sys/param.h> |
| 70 | #define JVM_MAXPATHLEN MAXPATHLEN |
| 71 | #endif |
| 72 | |
| 73 | |
| 74 | #ifdef _WINDOWS |
| 75 | static int getLastErrorString(char *buf, size_t len) |
| 76 | { |
| 77 | long errval; |
| 78 | |
| 79 | if ((errval = GetLastError()) != 0) |
| 80 | { |
| 81 | /* DOS error */ |
| 82 | size_t n = (size_t)FormatMessage( |
| 83 | FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, |
| 84 | NULL, |
| 85 | errval, |
| 86 | 0, |
| 87 | buf, |
| 88 | (DWORD)len, |
| 89 | NULL); |
| 90 | if (n > 3) { |
| 91 | /* Drop final '.', CR, LF */ |
| 92 | if (buf[n - 1] == '\n') n--; |
| 93 | if (buf[n - 1] == '\r') n--; |
| 94 | if (buf[n - 1] == '.') n--; |
| 95 | buf[n] = '\0'; |
| 96 | } |
| 97 | return (int)n; |
| 98 | } |
| 99 | |
| 100 | if (errno != 0) |
| 101 | { |
| 102 | /* C runtime error that has no corresponding DOS error code */ |
| 103 | strerror_s(buf, len, errno); |
| 104 | return strlen(buf); |
| 105 | } |
| 106 | return 0; |
| 107 | } |
| 108 | #endif /* _WINDOWS */ |
| 109 | |
| 110 | /* |
| 111 | * Class: sun_jvm_hotspot_asm_Disassembler |
| 112 | * Method: load_library |
| 113 | * Signature: (Ljava/lang/String;)L |
| 114 | */ |
| 115 | JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_asm_Disassembler_load_1library(JNIEnv * env, |
| 116 | jclass disclass, |
| 117 | jstring jrepath_s, |
| 118 | jstring libname_s) { |
| 119 | uintptr_t func = 0; |
| 120 | const char *error_message = NULL; |
| 121 | const char *jrepath = NULL; |
| 122 | const char *libname = NULL; |
| 123 | char buffer[JVM_MAXPATHLEN]; |
| 124 | |
| 125 | #ifdef _WINDOWS |
| 126 | HINSTANCE hsdis_handle = (HINSTANCE) NULL; |
| 127 | #else |
| 128 | void* hsdis_handle = NULL; |
| 129 | #endif |
| 130 | |
| 131 | jrepath = (*env)->GetStringUTFChars(env, jrepath_s, NULL); // like $JAVA_HOME/jre/lib/sparc/ |
| 132 | if (jrepath == NULL || (*env)->ExceptionOccurred(env)) { |
| 133 | return 0; |
| 134 | } |
| 135 | |
| 136 | libname = (*env)->GetStringUTFChars(env, libname_s, NULL); |
| 137 | if (libname == NULL || (*env)->ExceptionOccurred(env)) { |
| 138 | (*env)->ReleaseStringUTFChars(env, jrepath_s, jrepath); |
| 139 | return 0; |
| 140 | } |
| 141 | |
| 142 | /* Load the hsdis library */ |
| 143 | #ifdef _WINDOWS |
| 144 | hsdis_handle = LoadLibrary(libname); |
| 145 | if (hsdis_handle == NULL) { |
| 146 | snprintf(buffer, sizeof(buffer), "%s%s" , jrepath, libname); |
| 147 | hsdis_handle = LoadLibrary(buffer); |
| 148 | } |
| 149 | if (hsdis_handle != NULL) { |
| 150 | func = (uintptr_t)GetProcAddress(hsdis_handle, "decode_instructions_virtual" ); |
| 151 | } |
| 152 | if (func == 0) { |
| 153 | getLastErrorString(buffer, sizeof(buffer)); |
| 154 | error_message = buffer; |
| 155 | } |
| 156 | #else |
| 157 | hsdis_handle = dlopen(libname, RTLD_LAZY | RTLD_GLOBAL); |
| 158 | if (hsdis_handle == NULL) { |
| 159 | snprintf(buffer, sizeof(buffer), "%s%s" , jrepath, libname); |
| 160 | hsdis_handle = dlopen(buffer, RTLD_LAZY | RTLD_GLOBAL); |
| 161 | } |
| 162 | if (hsdis_handle != NULL) { |
| 163 | func = (uintptr_t)dlsym(hsdis_handle, "decode_instructions_virtual" ); |
| 164 | } |
| 165 | if (func == 0) { |
| 166 | error_message = dlerror(); |
| 167 | } |
| 168 | #endif |
| 169 | |
| 170 | (*env)->ReleaseStringUTFChars(env, libname_s, libname); |
| 171 | (*env)->ReleaseStringUTFChars(env, jrepath_s, jrepath); |
| 172 | |
| 173 | if (func == 0) { |
| 174 | /* Couldn't find entry point. error_message should contain some |
| 175 | * platform dependent error message. |
| 176 | */ |
| 177 | jclass eclass = (*env)->FindClass(env, "sun/jvm/hotspot/debugger/DebuggerException" ); |
| 178 | if ((*env)->ExceptionOccurred(env)) { |
| 179 | /* Can't throw exception, probably OOM, so silently return 0 */ |
| 180 | return (jlong) 0; |
| 181 | } |
| 182 | |
| 183 | (*env)->ThrowNew(env, eclass, error_message); |
| 184 | } |
| 185 | return (jlong)func; |
| 186 | } |
| 187 | |
| 188 | /* signature of decode_instructions_virtual from hsdis.h */ |
| 189 | typedef void* (*decode_func)(uintptr_t start_va, uintptr_t end_va, |
| 190 | unsigned char* start, uintptr_t length, |
| 191 | void* (*event_callback)(void*, const char*, void*), |
| 192 | void* event_stream, |
| 193 | int (*printf_callback)(void*, const char*, ...), |
| 194 | void* printf_stream, |
| 195 | const char* options, |
| 196 | int newline); |
| 197 | |
| 198 | /* container for call back state when decoding instructions */ |
| 199 | typedef struct { |
| 200 | JNIEnv* env; |
| 201 | jobject dis; |
| 202 | jobject visitor; |
| 203 | jmethodID handle_event; |
| 204 | jmethodID raw_print; |
| 205 | char buffer[4096]; |
| 206 | } decode_env; |
| 207 | |
| 208 | |
| 209 | /* event callback binding to Disassembler.handleEvent */ |
| 210 | static void* event_to_env(void* env_pv, const char* event, void* arg) { |
| 211 | jlong result = 0; |
| 212 | decode_env* denv = (decode_env*)env_pv; |
| 213 | JNIEnv* env = denv->env; |
| 214 | jstring event_string = (*env)->NewStringUTF(env, event); |
| 215 | if ((*env)->ExceptionOccurred(env)) { |
| 216 | return NULL; |
| 217 | } |
| 218 | |
| 219 | result = (*env)->CallLongMethod(env, denv->dis, denv->handle_event, denv->visitor, |
| 220 | event_string, (jlong) (uintptr_t)arg); |
| 221 | if ((*env)->ExceptionOccurred(env)) { |
| 222 | /* ignore exceptions for now */ |
| 223 | (*env)->ExceptionClear(env); |
| 224 | return NULL; |
| 225 | } |
| 226 | |
| 227 | return (void*)(uintptr_t)result; |
| 228 | } |
| 229 | |
| 230 | /* printing callback binding to Disassembler.rawPrint */ |
| 231 | static int printf_to_env(void* env_pv, const char* format, ...) { |
| 232 | jstring output; |
| 233 | va_list ap; |
| 234 | int cnt; |
| 235 | decode_env* denv = (decode_env*)env_pv; |
| 236 | JNIEnv* env = denv->env; |
| 237 | size_t flen = strlen(format); |
| 238 | const char* raw = NULL; |
| 239 | |
| 240 | if (flen == 0) return 0; |
| 241 | if (flen < 2 || |
| 242 | strchr(format, '%') == NULL) { |
| 243 | raw = format; |
| 244 | } else if (format[0] == '%' && format[1] == '%' && |
| 245 | strchr(format+2, '%') == NULL) { |
| 246 | // happens a lot on machines with names like %foo |
| 247 | flen--; |
| 248 | raw = format+1; |
| 249 | } |
| 250 | if (raw != NULL) { |
| 251 | jstring output = (*env)->NewStringUTF(env, raw); |
| 252 | if (!(*env)->ExceptionOccurred(env)) { |
| 253 | /* make sure that UTF allocation doesn't cause OOM */ |
| 254 | (*env)->CallVoidMethod(env, denv->dis, denv->raw_print, denv->visitor, output); |
| 255 | } |
| 256 | if ((*env)->ExceptionOccurred(env)) { |
| 257 | /* ignore exceptions for now */ |
| 258 | (*env)->ExceptionClear(env); |
| 259 | } |
| 260 | return (int) flen; |
| 261 | } |
| 262 | va_start(ap, format); |
| 263 | cnt = vsnprintf(denv->buffer, sizeof(denv->buffer), format, ap); |
| 264 | va_end(ap); |
| 265 | |
| 266 | output = (*env)->NewStringUTF(env, denv->buffer); |
| 267 | if (!(*env)->ExceptionOccurred(env)) { |
| 268 | /* make sure that UTF allocation doesn't cause OOM */ |
| 269 | (*env)->CallVoidMethod(env, denv->dis, denv->raw_print, denv->visitor, output); |
| 270 | } |
| 271 | |
| 272 | if ((*env)->ExceptionOccurred(env)) { |
| 273 | /* ignore exceptions for now */ |
| 274 | (*env)->ExceptionClear(env); |
| 275 | } |
| 276 | |
| 277 | return cnt; |
| 278 | } |
| 279 | |
| 280 | /* |
| 281 | * Class: sun_jvm_hotspot_asm_Disassembler |
| 282 | * Method: decode |
| 283 | * Signature: (Lsun/jvm/hotspot/asm/InstructionVisitor;J[BLjava/lang/String;J)V |
| 284 | */ |
| 285 | JNIEXPORT void JNICALL Java_sun_jvm_hotspot_asm_Disassembler_decode(JNIEnv * env, |
| 286 | jobject dis, |
| 287 | jobject visitor, |
| 288 | jlong startPc, |
| 289 | jbyteArray code, |
| 290 | jstring options_s, |
| 291 | jlong decode_instructions_virtual) { |
| 292 | jbyte *start = NULL; |
| 293 | jbyte *end = NULL; |
| 294 | jclass disclass = NULL; |
| 295 | const char *options = NULL; |
| 296 | decode_env denv; |
| 297 | |
| 298 | start = (*env)->GetByteArrayElements(env, code, NULL); |
| 299 | if ((*env)->ExceptionOccurred(env)) { |
| 300 | return; |
| 301 | } |
| 302 | end = start + (*env)->GetArrayLength(env, code); |
| 303 | options = (*env)->GetStringUTFChars(env, options_s, NULL); |
| 304 | if ((*env)->ExceptionOccurred(env)) { |
| 305 | (*env)->ReleaseByteArrayElements(env, code, start, JNI_ABORT); |
| 306 | return; |
| 307 | } |
| 308 | disclass = (*env)->GetObjectClass(env, dis); |
| 309 | |
| 310 | denv.env = env; |
| 311 | denv.dis = dis; |
| 312 | denv.visitor = visitor; |
| 313 | |
| 314 | /* find Disassembler.handleEvent callback */ |
| 315 | denv.handle_event = (*env)->GetMethodID(env, disclass, "handleEvent" , |
| 316 | "(Lsun/jvm/hotspot/asm/InstructionVisitor;Ljava/lang/String;J)J" ); |
| 317 | if ((*env)->ExceptionOccurred(env)) { |
| 318 | (*env)->ReleaseByteArrayElements(env, code, start, JNI_ABORT); |
| 319 | (*env)->ReleaseStringUTFChars(env, options_s, options); |
| 320 | return; |
| 321 | } |
| 322 | |
| 323 | /* find Disassembler.rawPrint callback */ |
| 324 | denv.raw_print = (*env)->GetMethodID(env, disclass, "rawPrint" , |
| 325 | "(Lsun/jvm/hotspot/asm/InstructionVisitor;Ljava/lang/String;)V" ); |
| 326 | if ((*env)->ExceptionOccurred(env)) { |
| 327 | (*env)->ReleaseByteArrayElements(env, code, start, JNI_ABORT); |
| 328 | (*env)->ReleaseStringUTFChars(env, options_s, options); |
| 329 | return; |
| 330 | } |
| 331 | |
| 332 | /* decode the buffer */ |
| 333 | (*(decode_func)(uintptr_t)decode_instructions_virtual)((uintptr_t) startPc, |
| 334 | startPc + end - start, |
| 335 | (unsigned char*)start, |
| 336 | end - start, |
| 337 | &event_to_env, (void*) &denv, |
| 338 | &printf_to_env, (void*) &denv, |
| 339 | options, 0 /* newline */); |
| 340 | |
| 341 | /* cleanup */ |
| 342 | (*env)->ReleaseByteArrayElements(env, code, start, JNI_ABORT); |
| 343 | (*env)->ReleaseStringUTFChars(env, options_s, options); |
| 344 | } |
| 345 | |