Path: blob/master/runtime/jextractnatives/jextractnatives.c
5986 views
/*******************************************************************************1* Copyright (c) 1991, 2021 IBM Corp. and others2*3* This program and the accompanying materials are made available under4* the terms of the Eclipse Public License 2.0 which accompanies this5* distribution and is available at https://www.eclipse.org/legal/epl-2.0/6* or the Apache License, Version 2.0 which accompanies this distribution and7* is available at https://www.apache.org/licenses/LICENSE-2.0.8*9* This Source Code may also be made available under the following10* Secondary Licenses when the conditions for such availability set11* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU12* General Public License, version 2 with the GNU Classpath13* Exception [1] and GNU General Public License, version 2 with the14* OpenJDK Assembly Exception [2].15*16* [1] https://www.gnu.org/software/classpath/license.html17* [2] http://openjdk.java.net/legal/assembly-exception.html18*19* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception20*******************************************************************************/2122#include "jni.h"2324#include "j9dbgext.h"25#include "j9protos.h"26#include "j9port.h"27#include "jextractnatives_internal.h"28#include "j9version.h"2930#include <stdarg.h>31#include <stdlib.h>3233#define CACHE_SIZE 10243435#define DEBUG_CACHE_STATISTICS 03637typedef struct dbgCacheElement {38UDATA address;39U_8 data[4096];40} dbgCacheElement;4142static JNIEnv *globalEnv;43static jobject globalDumpObj;44static jmethodID globalGetMemMid;45static jmethodID globalFindPatternMid;46static dbgCacheElement cache[CACHE_SIZE];4748static jint cacheIDs(JNIEnv* env, jobject dumpObj);49static jboolean validateDump(JNIEnv *env, jboolean disableBuildIdCheck);50static jint callFindPattern(U_8* pattern, jint patternLength, jint patternAlignment, jlong startSearchFrom, jlong* resultP);51static void callGetMemoryBytes(UDATA address, void *structure, UDATA size, UDATA *bytesRead);52static void readCachedMemory(UDATA address, void *structure, UDATA size, UDATA *bytesRead);53static void flushCache(void);5455void56dbgReadMemory(UDATA address, void *structure, UDATA size, UDATA *bytesRead)57{58if (address == 0) {59memset(structure, 0, size);60*bytesRead = 0;61return;62}6364readCachedMemory(address, structure, size, bytesRead);65if (*bytesRead != size) {66callGetMemoryBytes(address, structure, size, bytesRead);67}68}6970UDATA71dbgGetExpression(const char *args)72{73#ifdef WIN6474return (UDATA)_strtoui64(args, NULL, 16);75#else76return (UDATA)strtoul(args, NULL, 16);77#endif78}7980/*81* See dbgFindPatternInRange82*/83void*84dbgFindPattern(U_8 *pattern, UDATA patternLength, UDATA patternAlignment, U_8 *startSearchFrom, UDATA *bytesSearched)85{86jlong result = 0;8788*bytesSearched = 0;8990if (callFindPattern(pattern, (jint) patternLength, (jint) patternAlignment, (jlong) (UDATA) startSearchFrom, &result)) {91return NULL;92}9394*bytesSearched = (UDATA)-1;95if (result == (jlong)-1) {96return NULL;97} else {98return (void*)(UDATA)result;99}100}101102/*103* Find the J9RAS structure and validate that it is correct.104* This prevents jextract from one build or platform being used with a105* dump produced by a different build or platform.106*107* Return JNI_TRUE if the dump matches jextract.108* Return JNI_FALSE and set a Java exception if the dump does not match.109*/110static jboolean111validateDump(JNIEnv *env, jboolean disableBuildIdCheck)112{113jlong startFrom = 0;114jlong eyecatcher = 0;115char errBuf[256];116117PORT_ACCESS_FROM_VMC((J9VMThread*)env);118119jclass errorClazz = (*env)->FindClass(env, "java/lang/Error");120if (errorClazz == NULL) {121return JNI_FALSE;122}123124for(;;) {125J9RAS *ras = NULL;126const char *rasString = "J9VMRAS";127128if (callFindPattern((U_8*)rasString, sizeof(rasString), 8, startFrom, &eyecatcher)) {129(*env)->ThrowNew(env, errorClazz, "An error occurred while searching for the J9VMRAS eyecatcher");130return JNI_FALSE;131}132133if (eyecatcher == (jlong)-1) {134j9str_printf(PORTLIB,135errBuf, sizeof(errBuf),136"JVM anchor block (J9VMRAS) not found in dump. Dump may be truncated, corrupted or contains a partially initialized JVM.");137(*env)->ThrowNew(env, errorClazz, errBuf);138return JNI_FALSE;139}140141#if !defined(J9VM_ENV_DATA64)142if ((U_64)eyecatcher > (U_64)0xFFFFFFFF) {143j9str_printf(PORTLIB,144errBuf, sizeof(errBuf),145"J9RAS is out of range for a 32-bit pointer (0x%16.16llx). This version of jextract is incompatible with this dump.",146eyecatcher);147(*env)->ThrowNew(env, errorClazz, errBuf);148return JNI_FALSE;149}150#endif151/* Allocate this, since on 64-bit platforms we want to know now that we can't allocate152* the scratch space. This allows us to exit early with a simple error message.153*/154ras = dbgMallocAndRead(sizeof(J9RAS), (void *)(UDATA)eyecatcher);155if (ras != NULL) {156if (ras->bitpattern1 == 0xaa55aa55 && ras->bitpattern2 == 0xaa55aa55) {157if (ras->version != J9RASVersion) {158j9str_printf(PORTLIB,159errBuf, sizeof(errBuf),160"J9RAS.version is incorrect (found %u, expecting %u). This version of jextract is incompatible with this dump.",161ras->version,162J9RASVersion);163(*env)->ThrowNew(env, errorClazz, errBuf);164return JNI_FALSE;165}166if (ras->length != sizeof(J9RAS)) {167j9str_printf(PORTLIB,168errBuf, sizeof(errBuf),169"J9RAS.length is incorrect (found %u, expecting %u). This version of jextract is incompatible with this dump.",170ras->length,171sizeof(J9RAS));172(*env)->ThrowNew(env, errorClazz, errBuf);173return JNI_FALSE;174}175if (ras->buildID != J9UniqueBuildID) {176if (disableBuildIdCheck) {177j9tty_printf(PORTLIB,178"Ignoring incorrect J9RAS.buildID (found %llx, expecting %llx)."179" This version of jextract may be incompatible with this dump.\n",180ras->buildID,181(U_64)J9UniqueBuildID);182} else {183j9str_printf(PORTLIB,184errBuf, sizeof(errBuf),185"J9RAS.buildID is incorrect (found %llx, expecting %llx)."186" This version of jextract is incompatible with this dump"187" (use '-r' option to relax this check).",188ras->buildID,189(U_64)J9UniqueBuildID);190(*env)->ThrowNew(env, errorClazz, errBuf);191return JNI_FALSE;192}193}194195/* cache the value here so that dbgSniffForJavaVM doesn't need to duplicate this work */196dbgSetVM((J9JavaVM*)ras->vm);197return JNI_TRUE;198}199dbgFree(ras);200} else {201/* On 64-bit platforms, the code that tries to allocate the scratch space J9DBGEXT_SCRATCH_SIZE202* scratch space will have issued it's own informative error already.203*/204j9str_printf(PORTLIB,205errBuf, sizeof(errBuf),206"Cannot allocate %zu bytes of memory for initial RAS eyecatcher, cannot continue processing this dump.",207sizeof(J9RAS));208(*env)->ThrowNew(env, errorClazz, errBuf);209return JNI_FALSE;210}211212/* this isn't it -- look for the next occurrence */213startFrom = eyecatcher + 8;214}215}216217/*218* See dbgFindPatternInRange219*/220static jint221callFindPattern(U_8* pattern, jint patternLength, jint patternAlignment, jlong startSearchFrom, jlong* resultP)222{223jbyteArray patternArray = NULL;224jlong result = 0;225226if (!globalDumpObj || !globalFindPatternMid) {227return -1;228}229230patternArray = (*globalEnv)->NewByteArray(globalEnv, (jsize)patternLength);231if (patternArray == NULL) {232(*globalEnv)->ExceptionDescribe(globalEnv);233return -1;234}235236(*globalEnv)->SetByteArrayRegion(globalEnv, patternArray, 0, (jsize)patternLength, (jbyte*)pattern);237if ((*globalEnv)->ExceptionCheck(globalEnv)) {238(*globalEnv)->DeleteLocalRef(globalEnv, patternArray);239(*globalEnv)->ExceptionDescribe(globalEnv);240return -1;241}242243result = (*globalEnv)->CallLongMethod(globalEnv,244globalDumpObj,245globalFindPatternMid,246patternArray,247(jint)patternAlignment,248(jlong)startSearchFrom);249250(*globalEnv)->DeleteLocalRef(globalEnv, patternArray);251252if ((*globalEnv)->ExceptionCheck(globalEnv)) {253(*globalEnv)->ExceptionDescribe(globalEnv);254return -1;255}256257*resultP = result;258return 0;259}260261static jint262cacheIDs(JNIEnv* env, jobject dumpObj)263{264jclass cls = NULL;265266globalEnv = env;267globalDumpObj = dumpObj;268269if (!dumpObj) {270return -1;271}272273cls = (*env)->GetObjectClass(env, dumpObj);274if (!cls) {275return -1;276}277278globalGetMemMid = (*env)->GetMethodID(env, cls,"getMemoryBytes","(JI)[B");279if (!globalGetMemMid) {280return -1;281}282283globalFindPatternMid = (*env)->GetMethodID(env, cls,"findPattern","([BIJ)J");284if (!globalFindPatternMid) {285return -1;286}287288return 0;289}290291void JNICALL292Java_com_ibm_jvm_j9_dump_extract_Main_doCommand(JNIEnv *env, jobject obj, jobject dumpObj, jstring commandObject)293{294const char *command = (*env)->GetStringUTFChars(env, commandObject, 0);295PORT_ACCESS_FROM_VMC((J9VMThread*)env);296297if (command == NULL) {298return;299}300301if (cacheIDs(env, dumpObj)) {302return;303}304305/* hook the debug extension's malloc and free up to ours, so that it can benefit from -memorycheck */306OMRPORT_FROM_J9PORT(dbgGetPortLibrary())->mem_allocate_memory = OMRPORT_FROM_J9PORT(PORTLIB)->mem_allocate_memory;307OMRPORT_FROM_J9PORT(dbgGetPortLibrary())->mem_free_memory = OMRPORT_FROM_J9PORT(PORTLIB)->mem_free_memory;308OMRPORT_FROM_J9PORT(dbgGetPortLibrary())->port_control = OMRPORT_FROM_J9PORT(PORTLIB)->port_control;309310run_command(command);311312(*env)->ReleaseStringUTFChars(env, commandObject, command);313}314315/**316* Gets the environment pointer from the J9RAS structure.317*/318jlong JNICALL319Java_com_ibm_jvm_j9_dump_extract_Main_getEnvironmentPointer(JNIEnv * env, jobject obj, jobject dumpObj, jboolean disableBuildIdCheck)320{321J9JavaVM* vmPtr = NULL;322J9JavaVM* localVMPtr = NULL;323J9RAS* localRAS = NULL;324jlong toReturn = 0;325326if (cacheIDs(env, dumpObj)) {327goto end;328}329330if (!validateDump(env, disableBuildIdCheck)) {331goto end;332}333334vmPtr = dbgSniffForJavaVM();335if (!vmPtr) {336goto end;337}338339localVMPtr = dbgMallocAndRead(sizeof(J9JavaVM), (void *)(UDATA)vmPtr);340if (!localVMPtr) {341goto end;342}343344localRAS = dbgMallocAndRead(sizeof(J9RAS), (void *)(UDATA)localVMPtr->j9ras);345if (!localRAS) {346goto end;347}348349#if defined(J9VM_ENV_DATA64)350toReturn = (jlong)(IDATA)localRAS->environment;351#else352toReturn = (jlong)(IDATA)localRAS->environment & J9CONST64(0xFFFFFFFF);353#endif354355end:356flushCache();357dbgFreeAll();358359return toReturn;360}361362I_32363dbg_j9port_create_library(J9PortLibrary *portLib, J9PortLibraryVersion *version, UDATA size)364{365PORT_ACCESS_FROM_ENV(globalEnv);366367return PORTLIB->port_create_library(portLib, version, size);368}369370/*371* Need this to get it to link. This is how the debug extension code write messages372* to the platform debugger - or rather to stdout here when we are not running under the373* platform debugger.374*/375void376dbgWriteString(const char* message)377{378PORT_ACCESS_FROM_VMC((J9VMThread*)globalEnv);379380j9tty_printf(PORTLIB, "%s", message);381}382383static void384callGetMemoryBytes(UDATA address, void *structure, UDATA size, UDATA *bytesRead)385{386jbyteArray data = NULL;387jlong ja = address;388jint js = (jsize)size;389390*bytesRead = 0;391memset(structure, 0, size);392393/* ensure that size can be represented as a jsize */394if ((js < 0) || ((UDATA)js != size)) {395return;396}397398if (!globalDumpObj || !globalGetMemMid) {399return;400}401402/* we need to allocate another 1-3 local refs so make sure we can get them to satisfy -Xcheck:jni */403(*globalEnv)->EnsureLocalCapacity(globalEnv, 3);404if ((*globalEnv)->ExceptionCheck(globalEnv)) {405/* if we fail to allocate local ref storage, just fail since this definitely won't work */406(*globalEnv)->ExceptionClear(globalEnv);407return;408}409data = (jbyteArray)((*globalEnv)->CallObjectMethod(globalEnv, globalDumpObj, globalGetMemMid, ja, js));410if ((*globalEnv)->ExceptionCheck(globalEnv)) {411/* CMVC 110117: ExceptionDescribe causes an uncaught event so manually call printStackTrace() */412jthrowable exception = (*globalEnv)->ExceptionOccurred(globalEnv);413jclass exceptionClass = NULL;414jmethodID printStackTraceID = NULL;415416(*globalEnv)->ExceptionClear(globalEnv);417/* note that the error cases where we are missing an exception, a class or printStackTrace, we have no good solution */418exceptionClass = (*globalEnv)->GetObjectClass(globalEnv, exception);419printStackTraceID = (*globalEnv)->GetMethodID(globalEnv, exceptionClass, "printStackTrace", "()V");420(*globalEnv)->CallVoidMethod(globalEnv, exception, printStackTraceID);421/* if we throw while trying to print a stack trace, all bets are off in terms of how to handle that exception so just clear it */422(*globalEnv)->ExceptionClear(globalEnv);423(*globalEnv)->DeleteLocalRef(globalEnv, exception);424(*globalEnv)->DeleteLocalRef(globalEnv, exceptionClass);425return;426}427428if (data) {429jsize jbytesRead = (*globalEnv)->GetArrayLength(globalEnv, data);430if (jbytesRead > js) {431/* throw an exception here? */432} else {433(*globalEnv)->GetByteArrayRegion(globalEnv, data, 0, jbytesRead, structure);434}435(*globalEnv)->DeleteLocalRef(globalEnv, data);436*bytesRead = (UDATA)jbytesRead;437}438}439440/**441* Flush all elements from the small object memory cache.442*/443static void444flushCache(void)445{446int i = 0;447448for (i = 0; i < sizeof(cache) / sizeof(cache[0]); i++) {449/* invalidate this element */450cache[i].address = 0;451}452}453454/**455* This function implements a very simple caching scheme to accelerate the reading of small objects.456* A more sophisticated scheme is implemented in the Java code. This cache allows us to bypass457* the relatively expensive call-in to Java for most objects. During a normal jextract run we expect458* the cache to have a hit rate of over 90%.459*/460static void461readCachedMemory(UDATA address, void *structure, UDATA size, UDATA *bytesRead)462{463#if DEBUG_CACHE_STATISTICS464static UDATA hits = 0;465static UDATA total = 0;466#endif /* DEBUG_CACHE_STATISTICS */467468dbgCacheElement* thisElement = NULL;469UDATA lineStart = address & ~(UDATA)(sizeof(thisElement->data) - 1);470UDATA endAddress = address + size;471472#if DEBUG_CACHE_STATISTICS473if (((++total) % (16 * 1024)) == 0) {474dbgPrint("Cache hit rate: %.2f\n", (float)hits / (float)total);475}476#endif /* DEBUG_CACHE_STATISTICS */477478*bytesRead = 0;479480if (((lineStart + sizeof(thisElement->data)) >= endAddress) &&481(endAddress > address)) { /* check for arithmetic overflow */482UDATA cacheBytesRead = 0;483484thisElement = &cache[(lineStart / sizeof(thisElement->data)) % CACHE_SIZE];485486/* is the data cached at this slot? */487if (thisElement->address == lineStart) {488memcpy(structure, thisElement->data + (address - lineStart), size);489*bytesRead = size;490#if DEBUG_CACHE_STATISTICS491hits += 1;492#endif /* DEBUG_CACHE_STATISTICS */493return;494}495496/* it wasn't -- cache it now */497callGetMemoryBytes(lineStart, thisElement->data, sizeof(thisElement->data), &cacheBytesRead);498if (cacheBytesRead == sizeof(thisElement->data)) {499thisElement->address = lineStart;500memcpy(structure, thisElement->data + (address - lineStart), size);501*bytesRead = size;502} else {503/* invalidate this element */504thisElement->address = 0;505}506}507}508509510