├── .cproject ├── .gitignore ├── Agent.cpp ├── Agent.hpp ├── Agent.png ├── README.md ├── javajam.jar └── run.sh /.cproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 95 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Debug/ 2 | .project 3 | .settings 4 | .git 5 | .DS_Store 6 | 7 | -------------------------------------------------------------------------------- /Agent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Agent.cpp 3 | * 4 | * Created on: Jan 26, 2018 5 | * Author: shaharshocron 6 | */ 7 | 8 | // 9 | // Created by Rodrigo Agustin Peinado on 5/7/16. 10 | // Copyright © 2016 Rodrigo Agustin Peinado. All rights reserved. 11 | // 12 | #include "Agent.hpp" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "stdlib.h" 18 | 19 | using namespace std; 20 | 21 | #define countof(array) (sizeof(array)/sizeof(array[0])) 22 | 23 | using namespace std; 24 | 25 | bool setsecuritymanager_checked=false; 26 | 27 | // ------ 28 | static const char* DUMP_DIRECTORY = "/tmp/"; 29 | 30 | //static jrawMonitorID* mutexFlag; 31 | 32 | int main() { return 0; } 33 | 34 | static void check(jvmtiEnv *jvmti, jvmtiError errnum, const char *str) 35 | { 36 | if ( errnum != JVMTI_ERROR_NONE ) { 37 | char *errnum_str = NULL; 38 | jvmti->GetErrorName(errnum, &errnum_str); 39 | 40 | cout << "ERROR: JVMTI"; 41 | } 42 | } 43 | 44 | /* ------------------------------------------------------------------- 45 | 46 | static void enter_critical_section(jvmtiEnv *jvmti) 47 | { 48 | jvmtiError error = jvmti->RawMonitorEnter(mutex); 49 | check(jvmti, error, "Cannot enter with raw monitor"); 50 | } 51 | 52 | 53 | static void exit_critical_section(jvmtiEnv *jvmti) 54 | { 55 | jvmtiError error = jvmti->RawMonitorExit(mutex); 56 | check(jvmti, error, "Cannot exit with raw monitor"); 57 | } 58 | --------------------------------------------------------------------*/ 59 | 60 | static void printObject(jvmtiEnv *jvmti_env, jthread thread, jvmtiLocalVariableEntry* entry) { 61 | jvmtiError error; 62 | jint int_value_ptr; 63 | jlong long_value_ptr; 64 | jfloat float_value_ptr; 65 | jdouble double_value_ptr; 66 | jobject object_value_ptr; 67 | jint depht = 0; 68 | 69 | //cout << entry->signature << ":" << entry->name << endl; 70 | 71 | switch (entry->signature[0]) { 72 | case 'Z' : 73 | case 'B' : 74 | case 'S' : 75 | case 'C' : 76 | case 'I' : 77 | error = jvmti_env->GetLocalInt(thread, depht, entry->slot, &int_value_ptr); 78 | if(error == JVMTI_ERROR_NONE) { 79 | cout << "Type: Integer, Name: " << entry->name << ", Value: " << int_value_ptr << endl; 80 | } else { 81 | cout << "error: " << error << endl; 82 | } 83 | break; 84 | case 'J' : 85 | error = jvmti_env->GetLocalLong(thread, depht, entry->slot, &long_value_ptr); 86 | if(error == JVMTI_ERROR_NONE) { 87 | cout << "Type: Long, Name: " << entry->name << ", Value: " << long_value_ptr << endl; 88 | } else { 89 | cout << "error: " << error << endl; 90 | } 91 | break; 92 | case 'F' : 93 | error = jvmti_env->GetLocalFloat(thread, depht, entry->slot, &float_value_ptr); 94 | if(error == JVMTI_ERROR_NONE) { 95 | cout << "Type: Float, Name: " << entry->name << ", Value: " << float_value_ptr << endl; 96 | } else { 97 | cout << "error: " << error << endl; 98 | } 99 | break; 100 | case 'D' : 101 | error = jvmti_env->GetLocalDouble(thread, depht, entry->slot, &double_value_ptr); 102 | if(error == JVMTI_ERROR_NONE) { 103 | cout << "Type: Double, Name: " << entry->name << ", Value: " << double_value_ptr << endl; 104 | } else { 105 | cout << "error: " << error << endl; 106 | } 107 | break; 108 | case 'L' : 109 | error = jvmti_env->GetLocalObject(thread, depht, entry->slot, &object_value_ptr); 110 | if(error == JVMTI_ERROR_NONE) { 111 | // get object attributes 112 | 113 | cout << "Type: LocalObject, Name: " << entry->name << ", Length: " << entry->length 114 | << ", Value: " << object_value_ptr << endl; 115 | 116 | 117 | } else { 118 | cout << "error: " << error << endl; 119 | } 120 | break; 121 | } 122 | } 123 | 124 | static const char* getReferenceType(jint type) { 125 | switch (type) { 126 | case 0: 127 | return "Invalid Reference"; 128 | case 1: 129 | return "Local Reference"; 130 | case 2: 131 | return "Global Reference"; 132 | case 3: 133 | return "Weak Global reference"; 134 | default: 135 | return "Undefined:"; 136 | } 137 | } 138 | 139 | static int getLineNumberForMethod(jvmtiEnv *jvmti_env, jvmtiFrameInfo methodFrame) 140 | { 141 | jvmtiError error; 142 | jvmtiLineNumberEntry* table = NULL; 143 | jint entry_count = 0; 144 | int lineNumberCount; 145 | int lineNumber = -1; 146 | jlocation prevLocationId = -1; 147 | if (methodFrame.location == -1) 148 | return lineNumber; 149 | error = jvmti_env->GetLineNumberTable(methodFrame.method, 150 | &entry_count, &table); 151 | 152 | check(jvmti_env, error, "GetLineNumberTable: "); 153 | if (error == JVMTI_ERROR_NONE) 154 | { 155 | prevLocationId = table[0].start_location; 156 | for (lineNumberCount = 1; lineNumberCount < entry_count; lineNumberCount++) 157 | { 158 | if ((table[lineNumberCount].start_location > methodFrame.location) 159 | && (methodFrame.location >= prevLocationId)) 160 | { 161 | lineNumber = table[lineNumberCount - 1].line_number; 162 | break; 163 | } 164 | prevLocationId = table[lineNumberCount].start_location; 165 | } 166 | if (lineNumberCount == entry_count) 167 | { 168 | if (entry_count == 1) 169 | lineNumber = table[lineNumberCount - 1].line_number; 170 | else 171 | lineNumber = -1; 172 | } 173 | } 174 | return lineNumber; 175 | } 176 | 177 | static char * getExceptionMessage(JNIEnv* env, jobject exception) 178 | { 179 | jmethodID getMessageMethodId; 180 | jstring jExceptionMessage; 181 | int msgLength; 182 | char * msgArray = NULL; 183 | 184 | jclass clazz = env->FindClass("java/lang/Throwable"); 185 | getMessageMethodId = env->GetMethodID(clazz, "getMessage", "()Ljava/lang/String;"); 186 | if (getMessageMethodId != NULL) 187 | { 188 | jExceptionMessage = (jstring) env->CallObjectMethod(exception, getMessageMethodId); 189 | if (jExceptionMessage != NULL) 190 | { 191 | msgLength = env->GetStringLength(jExceptionMessage); 192 | if (msgLength > 0) 193 | { 194 | msgArray = (char *) malloc(msgLength + 1); // To be freed explicitly by the caller of this function 195 | if (msgArray == NULL) 196 | return NULL; 197 | env->GetStringUTFRegion(jExceptionMessage, 0, msgLength, msgArray); 198 | msgArray[msgLength] = '\0'; 199 | } 200 | } 201 | } 202 | return msgArray; 203 | } 204 | 205 | static void JNICALL Exception(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method,jlocation location,jobject exception,jmethodID catch_method,jlocation catch_location) 206 | { 207 | jint type; 208 | jint entryCount; 209 | jvmtiError error; 210 | jvmtiLocalVariableEntry* localVariableEntry; 211 | localVariableEntry = NULL; 212 | char * exceptionMessage = NULL; 213 | 214 | /*klass=jni_env->GetObjectClass(exception); 215 | jvmti_env->GetMethodName(method,&method_name,&method_signature,&generic_ptr_method); 216 | 217 | cout << "Method: " << method_name << ", Signature: " << method_signature << endl;*/ 218 | 219 | type= jni_env->GetObjectRefType(exception); 220 | 221 | cout << "Exception Reference Type: " << getReferenceType(type) << endl; 222 | 223 | if(type>0) 224 | { 225 | exceptionMessage = getExceptionMessage(jni_env, exception); 226 | cout << "Exception Message: " << exceptionMessage << endl; 227 | 228 | //---- 229 | jint num_frames=0; 230 | error = jvmti_env->GetFrameCount(thread, &num_frames); 231 | if (error != JVMTI_ERROR_NONE) { 232 | cout << "Could not get the frame count." << endl; 233 | return; 234 | } 235 | 236 | jvmtiFrameInfo frames[num_frames]; 237 | if(num_frames > 0) 238 | { 239 | jint count; 240 | error = jvmti_env->GetStackTrace(NULL, 0, 10, (jvmtiFrameInfo*)&frames, &count); 241 | cout << "Stacktrace: Frame count=" << count << " num of frames: " << num_frames << endl << "------------------------------------------------" << endl; 242 | 243 | if (error != JVMTI_ERROR_NONE) { 244 | printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, error); 245 | //describe(err); 246 | printf("\n"); 247 | } 248 | else if (error == JVMTI_ERROR_NONE && count >=1) { 249 | printf("Number of records filled: %d\n", count); 250 | char *methodName; 251 | //methodName = "yet_to_call()"; 252 | char *declaringClassName; 253 | jclass declaring_class; 254 | 255 | printf("Exception Stack Trace\n"); 256 | printf("=====================\n"); 257 | printf("Stack Trace Depth: %d\n", count); 258 | 259 | for (int i=0; i < count; i++) { 260 | error = jvmti_env->GetMethodName(frames[i].method, &methodName, NULL, NULL); 261 | if (error == JVMTI_ERROR_NONE) { 262 | error = jvmti_env->GetMethodDeclaringClass(frames[i].method, &declaring_class); 263 | error = jvmti_env->GetClassSignature(declaring_class, &declaringClassName, NULL); 264 | int lineNum = getLineNumberForMethod(jvmti_env, frames[i]); 265 | if (error == JVMTI_ERROR_NONE) { 266 | printf("at method %s( %d ) in class %s\n", methodName, lineNum, declaringClassName); 267 | } 268 | } 269 | 270 | error = jvmti_env->GetLocalVariableTable(frames[0].method, &entryCount, &localVariableEntry); 271 | 272 | if(error == JVMTI_ERROR_NONE) { 273 | 274 | jvmtiLocalVariableEntry* entry = localVariableEntry; 275 | cout << "Variables: " << entryCount << endl; 276 | 277 | for(int i = 0; i < entryCount; i++, entry++) { 278 | // print object's details 279 | printObject(jvmti_env, thread, entry); 280 | 281 | jvmti_env->Deallocate(reinterpret_cast(entry->signature)); 282 | jvmti_env->Deallocate(reinterpret_cast(entry->name)); 283 | jvmti_env->Deallocate(reinterpret_cast(entry->generic_signature)); 284 | } 285 | jvmti_env->Deallocate(reinterpret_cast(localVariableEntry)); 286 | } else if(error == JVMTI_ERROR_ABSENT_INFORMATION) { 287 | cout << "" << endl; 288 | } else { 289 | cout << "" << endl; 290 | } 291 | } 292 | } 293 | } 294 | } 295 | if(exceptionMessage != NULL) 296 | free(exceptionMessage); 297 | } 298 | 299 | //------- 300 | static void JNICALL loadClass(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jclass klass) 301 | { 302 | char *class_name; 303 | char *generic_ptr_class; 304 | 305 | jvmti_env->GetClassSignature(klass, &class_name,&generic_ptr_class); 306 | 307 | cout << "loadClass: Agent_Registered" << endl; 308 | jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION,(jthread)NULL); 309 | jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,JVMTI_EVENT_CLASS_PREPARE,(jthread)NULL); 310 | 311 | jvmti_env->Deallocate((unsigned char *)class_name); 312 | jvmti_env->Deallocate((unsigned char *)generic_ptr_class); 313 | } 314 | 315 | static bool startsWith(const char* str, const char* subStr) { 316 | // look for subStr at the start of str 317 | if ( strncmp(subStr, str, strlen(subStr)) == 0 ) 318 | return true; 319 | return false; 320 | } 321 | 322 | const char* clazzes[] = { "java", "sun", "scala" }; 323 | 324 | static bool isUserClass(const char* clz) { 325 | for(int i=0; i<3; i++) 326 | if( startsWith(clz, clazzes[i]) ) 327 | return false; 328 | return true; 329 | } 330 | 331 | static void JNICALL showClass(jvmtiEnv *jvmti, 332 | JNIEnv* env, 333 | jclass class_being_redefined, 334 | jobject loader, 335 | const char* name, 336 | jobject protection_domain, 337 | jint class_data_len, 338 | const unsigned char* class_data, 339 | jint* new_class_data_len, 340 | unsigned char** new_class_data) 341 | { 342 | //enter_critical_section(jvmti); { 343 | if(isUserClass(name)) 344 | { 345 | cout << "Loading Class: " << name << "\n"; 346 | std::string str(name); 347 | for(int i=0; iGetEnv((void **)&jvmti, JVMTI_VERSION_1); 378 | if (res != JNI_OK||jvmti==NULL) 379 | { 380 | cout << "ERROR: Unable to access JVMTI Version 1" << endl; 381 | } 382 | 383 | 384 | (void)memset(&capabilities,0, sizeof(capabilities)); 385 | 386 | capabilities.can_generate_exception_events=1; 387 | capabilities.can_get_line_numbers=1; 388 | capabilities.can_access_local_variables=1; 389 | capabilities.can_tag_objects=1; 390 | //--- 391 | capabilities.can_generate_all_class_hook_events = 1; 392 | 393 | jvmtiError error = jvmti->AddCapabilities(&capabilities); 394 | 395 | check(jvmti,error,"Unable to get necessary capabilities."); 396 | 397 | (void)memset(&callbacks,0, sizeof(callbacks)); 398 | 399 | //--- 400 | //callbacks.ClassPrepare = &loadClass; 401 | callbacks.ClassFileLoadHook = &showClass; 402 | callbacks.Exception=&Exception; 403 | 404 | jvmti->SetEventCallbacks(&callbacks, (jint)sizeof(callbacks)); 405 | 406 | //--- 407 | jvmti->SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, (jthread)NULL); 408 | //jvmti->SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_CLASS_PREPARE,(jthread)NULL); 409 | 410 | jvmti->SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_EXCEPTION,(jthread)NULL); 411 | 412 | //jvmti->CreateRawMonitor("agent data", &mutex); 413 | // check(jvmti, error, "Cannot create raw monitor"); 414 | 415 | return JNI_OK; 416 | } 417 | 418 | 419 | JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) 420 | { 421 | cout << "Agent_OnUnload" << endl; 422 | } 423 | 424 | 425 | -------------------------------------------------------------------------------- /Agent.hpp: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // tipiki.hpp 4 | // tipiki 5 | // 6 | // Created by Rodrigo Agustin Peinado on 5/7/16. 7 | // Copyright © 2016 Rodrigo Agustin Peinado. All rights reserved. 8 | // 9 | 10 | #ifndef agent_hpp 11 | #define agent_hpp 12 | 13 | #include 14 | 15 | #endif /* agent_hpp */ 16 | -------------------------------------------------------------------------------- /Agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LionHeart55/JavaAgent/3a71f94c2a96dbe5cbb721d1792329af38bf8e04/Agent.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agent 2 | 3 | ## Introduction 4 | This c++ project is an example of how to implement JVMTI library to attach to a JVM and gather data on exception in runtime. 5 | 6 | 1) tap into the load classes capabilities, get filename, read it ans save class file to /tmp/ 7 | 2) tap into exception capabilities 8 | - Get Exception message 9 | - Get Exception class 10 | - Get stack frames and for each frame 11 | - Get Class.Method. 12 | - Get line Number. 13 | - Get Variable names and values. 14 | 15 | ## Prerequisites 16 | 17 | Need to include the JVMTI header library located under: 18 | goto project-->properties choose C/C++ General--> Paths and symbols, then choose includes ==> GNU C++ 19 | then add these directories within your JDK folder for example: 20 | /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include 21 | /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin 22 | 23 | To build in eclipse choose project right click and click 'Build All' 24 | if this does not work you will need to create an new project in your IDE and import the sources. 25 | 26 | ## Usage 27 | 28 | java -agentpath:Debug/Agent -jar javajam.jar 29 | 30 | OR 31 | 32 | sudo chmod +x run.sh 33 | 34 | ./run.sh 35 | 36 | ![ScreenShot](./Agent.png?raw=true "screenshot") 37 | 38 | 39 | ## Sample output 40 | 41 | Exception Reference Type: Local Reference 42 | 43 | Exception Message: Throwing a test exception: IllegalThreadStateException 44 | 45 | Stacktrace: Frame count=2 num of frames: 2 46 | 47 | Number of records filled: 2 48 | 49 | Exception Stack Trace 50 | 51 | Stack Trace Depth: 2 52 | 53 | at method Throwing( 47 ) in class LMain/Main; 54 | 55 | Variables: 1 56 | 57 | Type: Integer, Name: value, Value: 8 58 | 59 | at method main( 19 ) in class LMain/Main; 60 | 61 | Variables: 1 62 | 63 | Type: Integer, Name: value, Value: 8 64 | -------------------------------------------------------------------------------- /javajam.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LionHeart55/JavaAgent/3a71f94c2a96dbe5cbb721d1792329af38bf8e04/javajam.jar -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | java -agentpath:Debug/Agent -jar javajam.jar 2 | --------------------------------------------------------------------------------