0

I have the following code to get the line numbers at the current stack frame locations when an exception is thrown that works but I figured it out by experimenting rather than through a specification. I only left the relevant code.

int max_frame_count = 50;
jvmtiFrameInfo frames[max_frame_count];
jint count;
(*jvmti_env)->GetStackTrace(jvmti_env, thread, 0, max_frame_count, frames, &count);
for (int loop = 0; loop < count; loop++) {
    jvmtiFrameInfo frame = frames[loop];
    jmethodID currentMethod = frame.method;
    (*jvmti_env)->GetMethodName(jvmti_env, currentMethod, &name_ptr, &signature_ptr, &generic_ptr);
    (*jvmti_env)->GetLineNumberTable(jvmti_env, currentMethod, &entry_count_ptr, &table_ptr);
    jint lineNumber = -1;
    int lineNumberCount;
    jlocation prevLocationId = -1;
    if (frame.location != -1) {
        for (int lineNumberLoop = entry_count_ptr - 1; lineNumberLoop >= 0; lineNumberLoop--) {
            jvmtiLineNumberEntry lineNumberEntry = table_ptr[lineNumberLoop];
            if (frame.location >= lineNumberEntry.start_location) {
                lineNumber = lineNumberEntry.line_number;
                break;
            }
        }
    }
}

GetLineNumberTable returns the line_number and the corresponding start_location for all lines of currentMethod. Now here is where my confusion starts: Why can't I match frame.location to a start_location returned by GetLineNumberTable? Is there any specification how these match? While my code seems to work, I can't belief that this is a solution that always works. It searches backwards for the first frame.location that is greater or equal to lineNumberEntry.start_location.

Thanks for any hints and pointers!

spreiter301
  • 314
  • 1
  • 12

2 Answers2

2

Your idea is right. To find a line number by jlocation, you need to find jvmtiLineNumberEntry with the closest start_location which is less or equal jlocation.

Actually, the official JVM TI samples do a similar search:

heapTracker.c

error = (*jvmti)->GetLineNumberTable(jvmti, finfo->method, &lineCount, &lineTable);
if ( error == JVMTI_ERROR_NONE ) {
    /* Search for line */
    lineNumber = lineTable[0].line_number;
    for ( i = 1 ; i < lineCount ; i++ ) {
        if ( finfo->location < lineTable[i].start_location ) {
            break;
        }
        lineNumber = lineTable[i].line_number;
    }
} else if ( error != JVMTI_ERROR_ABSENT_INFORMATION ) {
    check_jvmti_error(jvmti, error, "Cannot get method line table");
}

hprof_util.c

for ( i = start ; i < count ; i++ ) {
    if ( location < table[i].start_location ) {
        HPROF_ASSERT( ((int)location) < ((int)table[i].start_location) );
        break;
    }
    line_number = table[i].line_number;
}
apangin
  • 92,924
  • 10
  • 193
  • 247
0

After reading more about jlocation the code above should work for all situations.

jlocation values represent virtual machine bytecode indices--that is, offsets into the virtual machine code for a method.

Since one line of Java can generate multiple lines of byte code, it makes sense that lineNumberEntry.start_location can but doesn't have to match frame.location. lineNumberEntry.start_location is the first bytecode instruction of the java code line.

As an example if we have the following line number table:

  • line_number: 10, start_location: 10
  • line_number: 11, start_location: 18
  • line_number: 12, start_location: 22

And the following bytecode:

LINENUMBER 11 L3
18: NEW java/lang/IllegalStateException
19: DUP
20: INVOKESPECIAL java/lang/IllegalStateException.<init> ()V
21: ATHROW

And the exception is thrown on java line 11 at bytecode index 21, the code above will find the correct line number.

spreiter301
  • 314
  • 1
  • 12