Search code examples
javaandroidstack-tracebytecodedebug-symbols

Java class was compiled with debug symbols, yet no line number is shown in stacktrace?


I've received a log from someone using a Java library that I wrote, but perplexingly the stacktrace does not list the line number of my method.

This question seems to indicate that means the class was compiled without debug symbols, but if I take the .class file in question from the JAR they're using and run javap -v on it, I can see that was in fact compiled with debug symbols, and there's a LineNumberTable for the method in question:

      LineNumberTable:
        line 387: 0
        line 389: 4
        line 391: 11
        line 393: 23
        line 395: 30
        line 397: 62
        line 399: 69
        line 412: 101
        line 413: 107
        line 414: 116
        line 415: 122
        line 416: 134
        line 417: 141
        line 418: 150
        line 419: 156
        line 421: 168
        line 422: 178
        line 423: 192
        line 425: 206
        line 431: 214
        line 428: 217
        line 430: 219
        line 432: 224

So my question then becomes, what could cause the line number to not show up in the stacktrace even though I've confirmed that the .class file has the debug symbols? If it matters, this is in the context of Android. And no, it isn't ProGuard or something stripping out debug symbols, because line numbers are listed in other parts of the stack trace.


Solution

  • So I figured this out.

    A quick note, and I probably should have mentioned this in my question: the stack trace in question was not the result of a crash/exception but rather it was printed to show where the thread was at before the watchdog killed it because it was unresponsive.

    1. If not a deadlock, it was at least caused by long thread contention
    2. What a stack trace looks like when a thread is waiting to call a synchronized method when another thread is executing another synchronized method differs from ART vs. the JVM!

    On ART, the top stack frame will be shown as in the method without a line number, but in the JVM it will be shown as the first line in the method with a line number.

    Here's a "complete, minimal, reproducible" example for Android:

    public class MainActivity extends Activity
    {
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            launchThread("TH1");
            sleep(100);
            launchThread("TH2");
            sleep(20);
            dumpThreadTraces();
        }
    
        void launchThread(String name)
        {
            Thread thread = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    doThings();
                }
            });
    
            thread.setName(name);
            thread.start();
        }
    
        synchronized void doThings()
        {
            sleep(1000);
        }
    
        void dumpThreadTraces()
        {
            Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
    
            Set<Thread> threads = traces.keySet();
    
            for(Thread th : threads)
            {
                if(th.getName().startsWith("TH"))
                {
                    logStackTrace(th, traces.get(th));
                }
            }
        }
    
        void logStackTrace(Thread thread, StackTraceElement[] stackTrace)
        {
            System.out.printf("thread id=%d name=\"%s\"\n", thread.getId(), thread.getName());
            logStackFrames(stackTrace);
        }
    
        void logStackFrames(StackTraceElement[] stackTrace)
        {
            for (StackTraceElement frame : stackTrace)
            {
                System.out.printf("    at %s\n", frame.toString());
            }
        }
    
        void sleep(int millis)
        {
            try
            {
                Thread.sleep(millis);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
    

    When run, the following will be printed to logcat:

    I/System.out: thread id=2051 name="TH1"
    I/System.out:     at java.lang.Thread.sleep(Native Method)
    I/System.out:     at java.lang.Thread.sleep(Thread.java:371)
    I/System.out:     at java.lang.Thread.sleep(Thread.java:313)
    I/System.out:     at com.domain.helloworld.MainActivity.sleep(MainActivity.java:94)
    I/System.out:     at com.domain.helloworld.MainActivity.doThings(MainActivity.java:58)
    I/System.out:     at com.domain.helloworld.MainActivity$1.run(MainActivity.java:48)
    I/System.out:     at java.lang.Thread.run(Thread.java:761)
    I/System.out: thread id=2052 name="TH2"
    I/System.out:     at com.domain.helloworld.MainActivity.doThings(MainActivity.java)
    I/System.out:     at com.domain.helloworld.MainActivity$1.run(MainActivity.java:48)
    I/System.out:     at java.lang.Thread.run(Thread.java:761)
    

    Notice how for thread 2, the line number for the top stack trace element is not printed!

    at com.domain.helloworld.MainActivity.doThings(MainActivity.java)