Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java Debugging Internals - JPrime 2023

Java Debugging Internals - JPrime 2023

Learn how to build a debugger in Java using built-in Java concepts and APIs!

Yarden Laifenfeld

June 01, 2023
Tweet

More Decks by Yarden Laifenfeld

Other Decks in Programming

Transcript

  1. 2 Who Am I? • Software Engineer at Rookout •

    Java, Go, Ruby • C#, Python, JavaScript, C++ • Amateur sewist
  2. The Objective: Dynamic Snapshots • Dynamically add/remove snapshots on lines

    of code • Capture local variables • Capture stack trace 3
  3. The Objective: Dynamic Snapshots • Dynamically add/remove snapshots on lines

    of code • Capture local variables • Capture stack trace 4 Breakpoints?
  4. The Objective: Dynamic Snapshots • Dynamically add/remove snapshots • Capture

    local variables • Capture stack trace 5 Breakpoints? breakpoints on lines of code
  5. Production Grade Breakpoints • Minimum performance impact • Debug more

    than one instance at a time • Multiple people debugging the same instance 6
  6. JPDA - Java Platform Debugging Architecture The Java Platform Debugger

    Architecture (JPDA) consists of three interfaces designed for use by debuggers in development environments for desktop systems. - Oracle Documentation 8
  7. JPDA - Java Platform Debugging Architecture • JDI - Java

    Debug Interface • JDWP - Java Debug Wire Protocol • JVMTI - Java Virtual Machine Tool Interface 9
  8. JDI - Java Debug Interface • High level Java API

    • Inspect & Control • Connect remotely 10
  9. JDI - Simple Debugger Flow • Launch and connect ◦

    Get VirtualMachine instance ◦ Request to get events (breakpoint, watchpoint, threadStop, threadStart…) • When class is prepared, add a breakpoint • When breakpoint is hit, print variables 11
  10. Using JDI • No availability during collection • Zero control

    or visibility • Easy to make mistakes • Single instance debugging 12
  11. JPDA - Java Platform Debugging Architecture • JDI - Java

    Debug Interface • JDWP - Java Debug Wire Protocol • JVMTI - Java Virtual Machine Tool Interface 13
  12. JDWP - Java Debug Wire Protocol • A protocol •

    Used for communication between a debugger and the JVM which it debugs 14 JVM MyApp Debugger CLI JDWP
  13. Using JDWP • No availability during collection • Zero control

    or visibility • Easy to make mistakes • Single instance debugging • Managing multiple clients and connecting to multiple servers 15
  14. JVMTI - Java Virtual Machine Tool Interface • Native programming

    interface • Interacts directly with the JVM • Inspect & Control 16
  15. JPDA - Java Platform Debugging Architecture • JDI - Java

    Debug Interface • JDWP - Java Debug Wire Protocol • JVMTI - Java Virtual Machine Tool Interface 17
  16. JVMTI - Get Stack Trace /* Get Stack Trace */

    err = (*jvmti)->GetStackTrace(jvmti, thr, 0, 5, &frames, &count); if (err != JVMTI_ERROR_NONE) { printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err); describe(err); printf("\n"); } printf("Number of records filled: %d\n", count); if (err == JVMTI_ERROR_NONE && count >=1) { char *methodName; methodName = "yet_to_call()"; char *declaringClassName; jclass declaring_class; int i=0; printf("Exception Stack Trace\n"); printf("=====================\n"); printf("Stack Trace Depth: %d\n", count); for ( i=0; i < count; i++) { err = (*jvmti)->GetMethodName(jvmti, frames[i].method, &methodName, NULL, NULL); if (err == JVMTI_ERROR_NONE) { err = (*jvmti)->GetMethodDeclaringClass(jvmti, frames[i].method, &declaring_class); err = (*jvmti)->GetClassSignature(jvmti, declaring_class, &declaringClassName, NULL); if (err == JVMTI_ERROR_NONE) { printf("at method %s() in class %s\n", methodName, declaringClassName); } } } } 18 *A lot of code*
  17. Sample Program 24 1| public class HelloWorld { 2| public

    static void main(String[] args) { 3| while(true) { 4| System.out.println("Hello, World!"); 5| } 6| } 7| }
  18. Sample Program 25 • Build HelloWorld to JAR • Run:

    java -jar HelloWorld.jar • Output: Hello, World! Hello, World! ...
  19. Java Agent - Premain 26 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { System.out.println("Hello, JavaSummit!"); } }
  20. Java Agent - Premain 27 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { System.out.println("Hello, JavaSummit!"); } }
  21. Java Agent - Premain 28 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { System.out.println("Hello, JavaSummit!"); } }
  22. Java Agent - Premain 29 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { System.out.println("Hello, JavaSummit!"); } }
  23. Java Agent - Premain 30 • Build JavaAgent to JAR

    • Run with java agent: java -javaagent:JavaAgent.jar -jar HelloWorld.jar • Output: Hello, JavaSummit! Hello, World! ...
  24. Java Agent - ClassFileTransformer 32 JVM 32 | … 33

    | new Item() 34 | … Class Loader Get Item class bytecode Return Item class bytecode
  25. Java Agent - ClassFileTransformer 33 JVM 32 | … 33

    | new Item() 34 | … Class Loader Get Item class bytecode Return Item class bytecode Class File Transformer Return transformed Item class bytecode
  26. Java Agent - ClassFileTransformer 34 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { System.out.println("Hello, JavaSummit!"); } } • void addTransformer(ClassFileTransformer transformer);
  27. Java Agent - ClassFileTransformer 35 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
  28. Java Agent - ClassFileTransformer 36 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
  29. Java Agent - ClassFileTransformer 37 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
  30. Bytecode Manipulation 40 byte[] addPrintCall(byte[] classfileBuffer) { // Add call

    to System.out.println("Transformed!") // Return transformed bytecode }
  31. Java Agent - ClassFileTransformer 41 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className.contains("HelloWorld")) { return addPrintCall(classfileBuffer); } return classfileBuffer; } }
  32. Java Agent - ClassFileTransformer 42 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className.contains("HelloWorld")) { return addPrintCall(classfileBuffer); } return classfileBuffer; } }
  33. Java Agent - Premain 43 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { DebugTransformer transformer = new DebugTransformer(); instrumentation.addTransformer(transformer); } }
  34. Java Agent - Premain 44 public class JavaAgent { public

    static void premain(String agentArgs, Instrumentation instrumentation) throws Exception { DebugTransformer transformer = new DebugTransformer(); instrumentation.addTransformer(transformer); } }
  35. Java Agent - Premain 45 • Build JavaAgent to JAR

    • Run with java agent: java -javaagent:JavaAgent.jar -jar HelloWorld.jar • Output: Transformed! Hello, World! Hello, World! ...
  36. The Method: Inject a Hook • Inject a hook that

    will execute a specific function on the line of the snapshot 47 11: System.out.println(“Hello world!”); 12: return; Breakpoint()
  37. The Method: Inject a Hook • Pass all local variables

    to function 49 11: System.out.println(“Hello world!”); 12: return; Breakpoint(locals)
  38. Add Breakpoint 51 byte[] addBreakpoint(byte[] classfileBuffer, int lineno) { //

    Find where to insert Breakpoint call // Push local variables onto stack // Insert call to Breakpoint // Return transformed bytecode }
  39. Add Breakpoint 52 byte[] addBreakpoint(byte[] classfileBuffer, int lineno) { //

    Find where to insert Breakpoint call // Push local variables onto stack // Insert call to Breakpoint // Return transformed bytecode }
  40. Add Breakpoint 53 byte[] addBreakpoint(byte[] classfileBuffer, int lineno) { //

    Find where to insert Breakpoint call // Push local variables onto stack // Insert call to Breakpoint // Return transformed bytecode }
  41. Add Breakpoint 54 byte[] addBreakpoint(byte[] classfileBuffer, int lineno) { //

    Find where to insert Breakpoint call // Push local variables onto stack // Insert call to Breakpoint // Return transformed bytecode }
  42. Add Breakpoint 55 byte[] addBreakpoint(byte[] classfileBuffer, int lineno) { //

    Find where to insert Breakpoint call // Push local variables onto stack // Insert call to Breakpoint // Return transformed bytecode }
  43. Sample Program 56 1| public class HelloWorld { 2| public

    static void main(String[] args) { 3| while(true) { 4| System.out.println("Hello, World!"); 5| } 6| } 7| }
  44. Java Agent - ClassFileTransformer 57 public class DebugTransformer implements ClassFileTransformer

    { @Override public byte[] transform(ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className.contains("HelloWorld")) { return addBreakpoint(classfileBuffer, 4); } return classfileBuffer; } }
  45. Java Agent - Premain 58 • Build JavaAgent to JAR

    • Run with java agent: java -javaagent:JavaAgent.jar -jar HelloWorld.jar • Output: Breakpoint hit! [Ljava.lang.Object;@3d075dc0 Hello, World! ...
  46. Putting it all together 61 • Transform on demand •

    Add call to breakpoint function • Access locals as objects