3

I am using JNI and JVMTI with C++ with the goal of printing all class names of a target java application. The problem I am experiencing is that it crashes when trying to call the getName() method from inside each Java class.

    static jvmtiEnv* jvmtiEnv;
    static JNIEnv* Env;

    // The following code is located inside a method
    jint class_count;
    jclass* classes_ptr;
    jvmtiEnv->GetLoadedClasses(&class_count, &classes_ptr);
    Logger::Log("LoadedClassCount: " + std::to_string(class_count));

    for (int i = 0; i < class_count; i++) {
        jclass clazz = classes_ptr[i];

        if (clazz) {
            //Logger::Log("Class Number: " + std::to_string(i));
            jmethodID mid_getName = Env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
            if (!mid_getName) continue; // This method is not always available.

            Logger::Log("Pass 2");
            jobject classNameObj = Env->CallObjectMethod(clazz, mid_getName); // Crashes here
            if (!classNameObj) continue;
            Logger::Log("Pass 3");

            // Printing fix suggested by @Someprogrammerdude
            std::string str = Env->GetStringUTFChars((jstring) classNameObj, 0);
            Logger::Log(("ClassName: " + str).c_str());  // or Logger::Log("ClassName: " + str)
        }
    }

Also yes, the fields jvmtiEnv, and Env are initialized and work because I have been able to call other methods but cant seem to figure out how to print class names. The following is my logging method. I also print log method calls to console that is actively attached to the target java application but since my code crashes this part is useless, so I rely on also printing to a txt file.

void Logger::Log(std::string message)
{
    if (!Logger::Initialized) Logger::Init();
    std::cout << "[ LOG ] :: " + message << std::endl;

    std::ofstream log("C:\\Users\\me\\debug.txt", std::ofstream::app | std::ofstream::out);
    log << message << std::endl;
}
Botje
  • 26,269
  • 3
  • 31
  • 41
UnSure
  • 75
  • 6
  • Reminder: in C++, class names are not placed into the executable; they may be in a debug executable (or an associated debugging file). – Thomas Matthews Aug 16 '23 at 17:42
  • 1
    @ThomasMatthews I am trying to print all class names from a Java application from my C++ dll. – UnSure Aug 16 '23 at 17:44

2 Answers2

2

Let us say for the sake of demonstration that clazz is the Class instance that corresponds to the java.util.Vector class. In Java parlance this would be a Class<Vector>.

Thus, this line

jmethodID mid_getName = Env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");

tries to look up the getName method in java.util.Vector. This fails because, indeed, Vectors do not have a getName method. In the unlikely even that this would have returned a value for a given class C, you are only allowed to call that method on instances of class C. Yet you call it on the class C itself. That is why the JVM rightfully crashes.

Instead, you want to look up the getName method of Class itself, or in Java parlance, Class<Class> (and everywhere else, a "metaclass") and call that on all instances of Class<Class>, which are the Class in your classes_ptr.

There are lots of ways to get at this, but the easiest is probably just the following:

jclass cls_Class = Env->FindClass("java/lang/Class");
jmethodID mid_Class_getName = Env->GetMethodID(cls_Class, "getName", "()Ljava/lang/String;");

for (int i = 0; i < class_count; i++) {
   jclass clazz = classes_ptr[i];
   jstring classNameObj = (jstring) Env->CallObjectMethod(clazz, mid_Class_getName);
   ...
}
Botje
  • 26,269
  • 3
  • 31
  • 41
  • Yea I was just thinking about this because in Java you need to call getClass().getName() but I have no idea how to call both. Is it possible for me to do ``jmethodID getClass = Java::Env->GetMethodID(clazz, "getClass", "()Ljava/lang/Class;");`` and then somehow call that method and then follow it up with the getname call? Reason is the generic FindClass wont always work in my case, and I need to be able to get that class call from the class instance labeled ``clazz``. – UnSure Aug 16 '23 at 21:25
  • The "generic" FindClass will find the single `java.lang.Class` of which every `Class` object is an instance. (yes, it is an instance of itself.) If you do not trust me or the JVM you could call `Env->GetObjectClass(clazz)` and use that to look up `getName` instead. – Botje Aug 16 '23 at 21:26
  • Yes, you are right let me try that. – UnSure Aug 16 '23 at 21:27
  • You are the best thanks a ton it worked!!! – UnSure Aug 16 '23 at 21:35
1

"ClassName: " + *str is the same as "ClassName: " + str[0].

I.e. it adds the numeric value of the first character of str, and adds it to the pointer that points to the string "ClassName".

Essentially it's equivalent to &(("ClassName: ")[str[0]]). The resulting pointer will likely not be a valid string.

C++ is not Java, you can't append strings like that. The pointer you pass to the Log function will be invalid and using it will lead to undefined behavior, and likely crashes.

Instead of using a const char * for str, use std::string:

std::string str = Env->GetStringUTFChars((jstring) classNameObj, 0);
Logger::Log(("ClassName: " + str).c_str());  // or Logger::Log("ClassName: " + str) if it accepts a std::string object
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Yes, I see how I screwed up there but I just did not notice it yet because my code did not reach that far yet since it crashes when calling ``CallObjectMethod(clazz, mid_getName)``. I added another debug statement before it got to that part to ensure it would not be my string code that was causing the crash. In my debug file "Pass 2" was called but it crashed before it could call "Pass 3". – UnSure Aug 16 '23 at 17:53
  • @UnSure Then I assume that your logger class actually flushes the output passed to the `Log` function, so you don't have a false positive? Have you used an actual [debugger](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) to verify? – Some programmer dude Aug 16 '23 at 18:00
  • No, I have not tried an actual debugger however I know its not a printing issue since I have not had an issue with it ever (I included the code above). It is also crashing in the same exact spot even after I added your updated string code. Do you know of any possible issues that can occur when looping through every class and trying to print each classes name? – UnSure Aug 16 '23 at 18:18