Stack variables in lldb with custom generated metadata

Hello List!

I’m trying to add LLDB/DWARF debugging information to the RoboVM opensource fork [1]
Java AOT compiler. RoboVM uses the LLVM 3.6 (pre-release version) debugging format
and I got the basics working, but can’t get the stack variable inspection to work.
The IR is generated by RoboVM’s own implementation, not by the LLVM Cpp API.

The “fr v” command doesn’t show any local variables. Below the generated IR
for a method, the debugging info for this CU and the dwarfdump output.
I can’t see the void* pointer argument %env in lldb. (Metadata Lines !38 - !42)

Any hint what I’m doing wrong would be great, since I ran out of ideas.

Thanks for your time, and sorry if this is the wrong place to ask!
Florian

[1] https://github.com/florianf/robovm/tree/dwarf_debugging_branch

LLDB Version:
lldb-350.0.21.9

CLANG Version & Command:
Apple LLVM version 7.3.0 (clang-703.0.31)
clang++ -o Main -g -arch x86_64 -Wl,-filelist,objects0 -L vm/target/binaries/macosx/x86_64 -ObjC -exported_symbols_list exported_symbols -Wl,-no_implicit_dylibs -Wl,-dead_strip -fPIC -mmacosx-version-min=10.9 -Wl,-no_pie -lrobovm-bc-dbg -force_load vm/target/binaries/macosx/x86_64/librobovm-rt-dbg.a -lrobovm-debug-dbg -lrobovm-core-dbg -lgc-dbg -lpthread -ldl -lm -lz -liconv -lsqlite3 -framework Foundation -framework CoreServices

Generated IR for a method:

define weak i32 @"[J]Main.instanceMethod(I)I"(%Env* %p0, %Object* %p1, i32 %p2) nounwind noinline optsize {
label0:
%__envStack = alloca %Env*
store %Env* %p0, %Env** %__envStack
call void @“llvm.dbg.declare”(metadata %Env** %__envStack, metadata !39), !dbg !57
%r0 = alloca %Object*
%i0 = alloca i32
%r1 = alloca %Object*
%$r2 = alloca %Object*
%$r3 = alloca %Object*
%$i1 = alloca i32
%$i2 = alloca i32
%$i3 = alloca i32
call void @“checkso”()
store %Object* %p1, %Object** %r0, !dbg !58
store i32 %p2, i32* %i0, !dbg !63
%t0 = call %Object* @"[j]str_ndiiiddd_00[ldcstring]"(%Env* %p0), !dbg !51
store %Object* %t0, %Object** %r1, !dbg !56
%t1 = load %Object** %r0, !dbg !52
%t2 = call %Object* @"[j]Main.scAttr(Ltest/sub/SubClass;)[get]"(%Env* %p0, %Object* %t1), !dbg !64
store %Object* %t2, %Object** %$r2, !dbg !45
%t3 = load %Object** %$r2, !dbg !65
%t4 = call i8 @“checknull”(%Env* %p0, %Object* %t3), !dbg !43
call void @"[j]test.sub.SubClass.throwSomething()V[Invokevirtual(Main,test/sub/SubClass)]"(%Env* %p0, %Object* %t3), !dbg !50
%t5 = call %Object* @"[j]str_777_00[ldcstring]"(%Env* %p0), !dbg !49
store %Object* %t5, %Object** %$r3, !dbg !46
%t6 = load %Object** %$r3, !dbg !67
%t7 = call i32 @"[j]java.lang.String.length()I[Invokevirtual(Main,java/lang/String)]"(%Env* %p0, %Object* %t6), !dbg !54
store i32 %t7, i32* %$i1, !dbg !59
%t8 = load %Object** %r1, !dbg !55
%t9 = call i32 @"[j]java.lang.String.length()I[Invokevirtual(Main,java/lang/String)]"(%Env* %p0, %Object* %t8), !dbg !44
store i32 %t9, i32* %$i2, !dbg !60
%t11 = load i32* %$i1, !dbg !53
%t12 = load i32* %$i2, !dbg !61
%t10 = add i32 %t11, %t12, !dbg !47
store i32 %t10, i32* %$i3, !dbg !62
%t13 = load i32* %$i3, !dbg !66
ret i32 %t13, !dbg !48
}

Debugging Metadata:
llvm.dbg.cu = !{!7}
!llvm.module.flags = !{!3, !4}

!0 = !{}
!1 = !{!“Main.java”, !"/Users/generalsolutions/Documents/runtime-EclipseApplication/RobovmConsole/src/main/java/"}
!2 = !{!18, !38, !68, !78}
!3 = !{i32 2, !“Dwarf Version”, i32 2}
!4 = !{i32 2, !“Debug Info Version”, i32 2}
!5 = !{!“0x29”, !1} ; [ DW_TAG_file_type ] [ /Users/generalsolutions/Documents/runtime-EclipseApplication/RobovmConsole/src/main/java/Main.java ]
!6 = !{!“0x258”} ; [ DW_TAG_expression ]
!7 = !{!“0x11\0011\00RoboVM 2.2.0-SNAPSHOT\001\00\000\00\001”, !1, !0, !0, !2, !0, !0} ; [ DW_TAG_compile_unit ] [ /Users/generalsolutions/Documents/runtime-EclipseApplication/RobovmConsole/src/main/java/Main.java ] [ DW_LANG_Java ]
!8 = !{!“0x24\00boolean\000\008\008\000\000\002”, null, null} ; [ DW_TAG_base_type ] [ boolean ] [ line 0, size 8, align 8, offset 0, enc DW_ATE_boolean ]
!9 = !{!“0x24\00byte\000\008\008\000\000\006”, null, null} ; [ DW_TAG_base_type ] [ byte ] [ line 0, size 8, align 8, offset 0, enc DW_ATE_signed_char ]
!10 = !{!“0x24\00short\000\0016\0016\000\000\005”, null, null} ; [ DW_TAG_base_type ] [ short ] [ line 0, size 16, align 16, offset 0, enc DW_ATE_signed ]
!11 = !{!“0x24\00char\000\0016\0016\000\000\007”, null, null} ; [ DW_TAG_base_type ] [ char ] [ line 0, size 16, align 16, offset 0, enc DW_ATE_unsigned ]
!12 = !{!“0x24\00int\000\0032\0032\000\000\005”, null, null} ; [ DW_TAG_base_type ] [ int ] [ line 0, size 32, align 32, offset 0, enc DW_ATE_signed ]
!13 = !{!“0x24\00long\000\0064\0064\000\000\005”, null, null} ; [ DW_TAG_base_type ] [ long ] [ line 0, size 64, align 64, offset 0, enc DW_ATE_signed ]
!14 = !{!“0x24\00float\000\0032\0032\000\000\004”, null, null} ; [ DW_TAG_base_type ] [ float ] [ line 0, size 32, align 32, offset 0, enc DW_ATE_float ]
!15 = !{!“0x24\00double\000\0064\0064\000\000\004”, null, null} ; [ DW_TAG_base_type ] [ double ] [ line 0, size 64, align 64, offset 0, enc DW_ATE_float ]
!16 = !{!“0xf\00Env\000\0064\0064\000\000”, null, null, null} ; [ DW_TAG_pointer_type ] [ Env ] [ line 0, size 64, align 64, offset 0 ] [ from ]
!17 = !{!“0xf\00Object\000\0064\0064\000\000”, null, null, null} ; [ DW_TAG_pointer_type ] [ Object ] [ line 0, size 64, align 64, offset 0 ] [ from ]
!18 = !{!“0x2e\00[J]Main.(I)V\00[J]Main.(I)V\00\007\000\001\000\000\00256\000\007”, !1, !5, !22, null, void (%Env*, %Object*, i32)* @"[J]Main.(I)V", null, null, !20} ; [ DW_TAG_subprogram ] [ line 7 ] [ def ] [ [J]Main.(I)V ]
!19 = !{!“0x101\00env\0016777223\000”, !18, !5, !17} ; [ DW_TAG_arg_variable ] [ env ] [ line 7 ]
!20 = !{}
!21 = !{null, !16, !17, !12}
!22 = !{!“0x15\00\000\000\000\000\000\000”, null, null, null, !21, null, null, null} ; [ DW_TAG_subroutine_type ] [ line 0, size 0, align 0, offset 0 ] [ from ]
!23 = !{i32 9, i32 0, !18, null}
!24 = !{i32 7, i32 0, !18, null}
!25 = !{i32 8, i32 0, !18, null}
!26 = !{i32 9, i32 0, !18, null}
!27 = !{i32 10, i32 0, !18, null}
!28 = !{i32 9, i32 0, !18, null}
!29 = !{i32 8, i32 0, !18, null}
!30 = !{i32 7, i32 0, !18, null}
!31 = !{i32 7, i32 0, !18, null}
!32 = !{i32 9, i32 0, !18, null}
!33 = !{i32 7, i32 0, !18, null}
!34 = !{i32 7, i32 0, !18, null}
!35 = !{i32 9, i32 0, !18, null}
!36 = !{i32 9, i32 0, !18, null}
!37 = !{i32 9, i32 0, !18, null}
!38 = !{!“0x2e\00[J]Main.instanceMethod(I)I\00[J]Main.instanceMethod(I)I\00\0013\000\001\000\000\00256\000\0013”, !1, !5, !42, null, i32 (%Env*, %Object*, i32)* @"[J]Main.instanceMethod(I)I", null, null, !40} ; [ DW_TAG_subprogram ] [ line 13 ] [ def ] [ [J]Main.instanceMethod(I)I ]
!39 = !{!“0x101\00env\0016777229\000”, !38, !5, !17} ; [ DW_TAG_arg_variable ] [ env ] [ line 13 ]
!40 = !{}
!41 = !{!12, !16, !17, !12}
!42 = !{!“0x15\00\000\000\000\000\000\000”, null, null, null, !41, null, null, null} ; [ DW_TAG_subroutine_type ] [ line 0, size 0, align 0, offset 0 ] [ from ]
!43 = !{i32 14, i32 0, !38, null}
!44 = !{i32 15, i32 0, !38, null}
!45 = !{i32 14, i32 0, !38, null}
!46 = !{i32 15, i32 0, !38, null}
!47 = !{i32 15, i32 0, !38, null}
!48 = !{i32 15, i32 0, !38, null}
!49 = !{i32 15, i32 0, !38, null}
!50 = !{i32 14, i32 0, !38, null}
!51 = !{i32 13, i32 0, !38, null}
!52 = !{i32 14, i32 0, !38, null}
!53 = !{i32 15, i32 0, !38, null}
!54 = !{i32 15, i32 0, !38, null}
!55 = !{i32 15, i32 0, !38, null}
!56 = !{i32 13, i32 0, !38, null}
!57 = !{i32 13, i32 0, !38, null}
!58 = !{i32 13, i32 0, !38, null}
!59 = !{i32 15, i32 0, !38, null}
!60 = !{i32 15, i32 0, !38, null}
!61 = !{i32 15, i32 0, !38, null}
!62 = !{i32 15, i32 0, !38, null}
!63 = !{i32 13, i32 0, !38, null}
!64 = !{i32 14, i32 0, !38, null}
!65 = !{i32 14, i32 0, !38, null}
!66 = !{i32 15, i32 0, !38, null}
!67 = !{i32 15, i32 0, !38, null}
!68 = !{!“0x2e\00[J]Main.anotherMethod(JJLjava/lang/String;)I\00[J]Main.anotherMethod(JJLjava/lang/String;)I\00\0019\000\001\000\000\00256\000\0019”, !1, !5, !72, null, i32 (%Env*, i64, i64, %Object*)* @"[J]Main.anotherMethod(JJLjava/lang/String;)I", null, null, !70} ; [ DW_TAG_subprogram ] [ line 19 ] [ def ] [ [J]Main.anotherMethod(JJLjava/lang/String;)I ]
!69 = !{!“0x101\00env\0016777235\000”, !68, !5, !17} ; [ DW_TAG_arg_variable ] [ env ] [ line 19 ]
!70 = !{}
!71 = !{!12, !16, !17, !13, !13, !17}
!72 = !{!“0x15\00\000\000\000\000\000\000”, null, null, null, !71, null, null, null} ; [ DW_TAG_subroutine_type ] [ line 0, size 0, align 0, offset 0 ] [ from ]
!73 = !{i32 19, i32 0, !68, null}
!74 = !{i32 19, i32 0, !68, null}
!75 = !{i32 19, i32 0, !68, null}
!76 = !{i32 19, i32 0, !68, null}
!77 = !{i32 19, i32 0, !68, null}
!78 = !{!“0x2e\00[J]Main.main([Ljava/lang/String;)V\00[J]Main.main([Ljava/lang/String;)V\00\0024\000\001\000\000\00256\000\0024”, !1, !5, !82, null, void (%Env*, %Object*)* @"[J]Main.main([Ljava/lang/String;)V", null, null, !80} ; [ DW_TAG_subprogram ] [ line 24 ] [ def ] [ [J]Main.main([Ljava/lang/String;)V ]
!79 = !{!“0x101\00env\0016777240\000”, !78, !5, !17} ; [ DW_TAG_arg_variable ] [ env ] [ line 24 ]
!80 = !{}
!81 = !{null, !16, !17, !17}
!82 = !{!“0x15\00\000\000\000\000\000\000”, null, null, null, !81, null, null, null} ; [ DW_TAG_subroutine_type ] [ line 0, size 0, align 0, offset 0 ] [ from ]
!83 = !{i32 29, i32 0, !78, null}
!84 = !{i32 30, i32 0, !78, null}
!85 = !{i32 45, i32 0, !78, null}
!86 = !{i32 24, i32 0, !78, null}
!87 = !{i32 29, i32 0, !78, null}
!88 = !{i32 30, i32 0, !78, null}
!89 = !{i32 24, i32 0, !78, null}
!90 = !{i32 29, i32 0, !78, null}
!91 = !{i32 29, i32 0, !78, null}
!92 = !{i32 29, i32 0, !78, null}
!93 = !{i32 24, i32 0, !78, null}
!94 = !{i32 30, i32 0, !78, null}
!95 = !{i32 29, i32 0, !78, null}
!96 = !{i32 24, i32 0, !78, null}

LLDB Backtrace:
frame #3 is implemented in assembler, frame #0 is the method whith the %env arg:

  • thread #1: tid = 0xc6189, 0x000000010052861f Main`[J]Main.instanceMethod(I)I at Main.java:13, queue = ‘com.apple.main-thread’, stop reason = breakpoint 1.1
  • frame #0: 0x000000010052861f Main[J]Main.instanceMethod(I)I at Main.java:13 frame #1: 0x0000000100528586 Main[j]Main.instanceMethod(I)I[Invokevirtual(Main,Main)] + 9
    frame #2: 0x00000001005287a8 Main[J]Main.main([Ljava/lang/String;)V + 101 at Main.java:30 frame #3: 0x0000000100bd763e Main_call0 + 142 at call0-darwin-x86_64.s:80
    frame #4: 0x0000000100bbb661 MaincallVoidMethod(env=0x0000000101f4afa0, callInfo=0x00007fff5fbff500) + 145 at method.c:640 frame #5: 0x0000000100bc1cc2 MainrvmCallVoidClassMethodA(env=0x0000000101f4afa0, clazz=0x0000000103885580, method=0x000000010388f550, args=0x0000000103869fa0) + 514 at method.c:1177
    frame #6: 0x0000000100bc1d32 MainrvmCallVoidClassMethodV(env=0x0000000101f4afa0, clazz=0x0000000103885580, method=0x000000010388f550, args=0x00007fff5fbff810) + 82 at method.c:1183 frame #7: 0x0000000100bc1ead MainrvmCallVoidClassMethod(env=0x0000000101f4afa0, clazz=0x0000000103885580, method=0x000000010388f550) + 365 at method.c:1189
    frame #8: 0x0000000100bae158 MainrvmRun(env=0x0000000101f4afa0) + 344 at init.c:498 frame #9: 0x0000000100b984a2 Mainbcmain(argc=2, argv=0x00007fff5fbff918) + 178 at bc.c:97
    frame #10: 0x0000000100b9d87b Mainmain(argc=2, argv=0x00007fff5fbff918) + 27 at bc.c:103 frame #11: 0x00007fff886005ad libdyld.dylibstart + 1
    frame #12: 0x00007fff886005ad libdyld.dylib`start + 1

dwarfdump output:

Since your compile unit is Java:

    0x0000000b: TAG_compile_unit [1] *
                 AT_producer( "RoboVM 2.2.0-SNAPSHOT" )
                 AT_language( DW_LANG_Java )
                 AT_name( "Main.java" )
                 AT_stmt_list( 0x00000000 )
                 AT_comp_dir( "/Users/generalsolutions/Documents/runtime-EclipseApplication/RobovmConsole/src/main/java/" )
                 AT_APPLE_optimized( 0x01 )
                 AT_low_pc( 0x0000000000000000 )
                 AT_ranges( 0x00000000
                    [0x00000000000006f0 - 0x000000000000080b)
                    [0x0000000000000878 - 0x0000000000000893)
                    [0x0000000000000893 - 0x0000000000000901)
                     End )

The "DWARFASTParserJava" class will be used to parse any types. I would check the following function:

lldb::TypeSP
DWARFASTParserJava::ParseTypeFromDWARF(
  const lldb_private::SymbolContext &sc,
  const DWARFDIE &die,
  lldb_private::Log *log,
  bool *type_is_new_ptr);

If you are doing "frame variable" and you know the variable you are trying to view you can step through the DWARFASTParserJava::ParseTypeFromDWARF() and make sure that a valid type is made. For example, if you are looking at the "env" variable from:

    0x0000002b: TAG_subprogram [2] *
                     AT_low_pc( 0x00000000000006f0 )
                     AT_high_pc( 0x000000000000076f )
                     AT_frame_base( rbp )
                     AT_name( "[J]Main.<init>(I)V" )
                     AT_decl_file( "Main.java" )
                     AT_decl_line( 7 )
                     AT_external( 0x01 )

    0x00000045: TAG_formal_parameter [3]
                         AT_location( fbreg -8 )
                         AT_name( "env" )
                         AT_decl_file( "Main.java" )
                         AT_decl_line( 7 )
                         AT_type( {0x000000de} ( Object ) )

    0x00000053: NULL

We can see that tthe type for "env" is the type AT_type is 0x000000de. If we look at 0x000000de:

    0x000000de: TAG_pointer_type [6]
                     AT_name( "Object" )

After looking at DWARFASTParserJava::ParseTypeFromDWARF(), there is no handler for "TAG_pointer_type" and thus no CompilerType will be made for "0x000000de" and this is why you aren't seeing anything. Not sure how you would re-create a variable from this debug info? But seems like you will need to figure out how to make a type for this and you will then see your variable. I will leave this to the Java experts.

Greg Clayton