Better jdb integration


#1

Recently I have had to debug our server with jdb.
I could not use remote debug or sdb(it require sources on target machine) for security reason.

The most difficult task was to set correct breakpoint( `stop at class_id:line’ ). It was a real challenge for me, to find out anonymous classes and its source lines with javap :)))

So I have done simple utility which allow to do something like:

>jdb -attach jdbconn
>stop in ru.bitec.app.gtk.ExampleDebug$.main
>resume
>eval java.lang.Class.forName("ru.bitec.Jdb")
>print ru.bitec.Jdb.parseClasses("Example")

print ru.bitec.Jdb.parseClasses("Example")
 ru.bitec.Jdb.parseClasses("Example") = "
ExampleDebug.scala
0007-0008:ru.bitec.app.gtk.ExampleDebug
0010-0011:ru.bitec.app.gtk.ExampleDebug$$anon$1
          ru.bitec.app.gtk.ExampleDebug
0013-0015:ru.bitec.app.gtk.ExampleDebug$$anon$1
0023-0023:ru.bitec.app.gtk.ExampleDebug$
          ru.bitec.app.gtk.ExampleDebug
"
>stop at ru.bitec.app.gtk.ExampleDebug$$anon$1:14

I think it will be greate if such helper exists in a standard scala package.

I used asm to do it:

  def parseClasses(name:String):mutable.HashMap[String,
    mutable.HashMap[Int,mutable.Set[String]]] = {
    val classLoader = this.getClass.getClassLoader
    val stringBuilder = StringBuilder.newBuilder
    val s = new Reflections(ConfigurationBuilder.build(
      classLoader,
      ClasspathHelper.forPackage("some.root"),new ResourcesScanner()
    )).getResources(Pattern.compile(s".*$name.*.class")).asScala.toSet
    val map = mutable.HashMap.empty[String,
      mutable.HashMap[Int,mutable.Set[String]]]
    for(c <-s; s <- managed(classLoader.getResourceAsStream(c))){
      val reader = new ClassReader(s)
      reader.accept(
        new ClassVisitor(Opcodes.ASM7) {
          var source:String = _
          override def visitSource(source: String, debug: String): Unit = {
            this.source = source
            map.getOrElseUpdate(source,mutable.HashMap.empty)
            super.visitSource(source, debug)
          }
          override def visitMethod(access: Int, name: String, descriptor: String, signature: String, exceptions: Array[String]): MethodVisitor = {
            super.visitMethod(access,name,descriptor,signature,exceptions)
            new MethodVisitor(Opcodes.ASM7) {
              override def visitLineNumber(line: Int, start: Label): Unit = {
                val lineMap = map(source)
                val classSet = lineMap.getOrElseUpdate(line, mutable.Set.empty[String])
                classSet.add(c)
                super.visitLineNumber(line, start)
              }
            }
          }
        }, 0)
    }
    map
  }