excluded = new ArrayList<>();
+
+ public ThreadOriginTransformer(final String argument)
+ {
+ log("Arg: " + argument);
+
+ if(argument != null)
+ {
+ this.excluded.addAll(Arrays.asList(argument.split(",")));
+ }
+
+ log("Ignoring excluded: " + String.join(",", this.excluded));
+ }
+
+ /**
+ * Add agent
+ *
+ * see also src/main/resources/META-INF/MANIFEST.MF
+ *
+ */
+ public static void premain(final String agentArgument, final Instrumentation instrumentation)
+ {
+ try
+ {
+ threadClazz = ClassPool.getDefault().get("java.lang.Thread");
+ if(threadClazz == null)
+ {
+ throw new IllegalStateException("Unable to find Thread.class");
+ }
+ }
+ catch(final Exception ex)
+ {
+ log("Failed to instrument Thread class: " + ex);
+ throw new IllegalStateException(ex);
+ }
+
+ instrumentation.addTransformer(new ThreadOriginTransformer(agentArgument));
+
+ log("Trying to retransform loaded classes");
+ long unmodifiable = 0;
+ long success = 0;
+ for(final Class> loadedClazz : instrumentation.getAllLoadedClasses())
+ {
+ if(loadedClazz.getName().startsWith("software.xdev.tools")
+ || loadedClazz.getName().startsWith("javassist"))
+ {
+ log("Ignoring " + loadedClazz.getName());
+ continue;
+ }
+
+ try
+ {
+ instrumentation.retransformClasses(loadedClazz);
+ success++;
+ }
+ catch(final UnmodifiableClassException e)
+ {
+ unmodifiable++;
+ }
+ }
+ log("Retransformed loaded classes; " + success + "x successful, " + unmodifiable + "x unmodifiable");
+ }
+
+ @SuppressWarnings({"PMD.CognitiveComplexity", "PMD.NPathComplexity"})
+ @Override
+ public byte[] transform(
+ final ClassLoader loader,
+ final String className,
+ final Class> clazz,
+ final java.security.ProtectionDomain domain,
+ final byte[] bytes)
+ {
+ byte[] resultingBytes = bytes;
+ if(className == null)
+ {
+ log("ClassName was null; Class=" + clazz.getCanonicalName());
+ return resultingBytes;
+ }
+
+ if(this.excluded.stream().anyMatch(className::startsWith))
+ {
+ log("Excluded class=" + className);
+ return resultingBytes;
+ }
+
+ try
+ {
+ final ClassPool classPool = ClassPool.getDefault();
+ final CtClass classUnderTransformation = classPool.makeClass(new java.io.ByteArrayInputStream(bytes));
+ if(classUnderTransformation == null)
+ {
+ return resultingBytes;
+ }
+
+ classUnderTransformation.instrument(new ExprEditor()
+ {
+ @Override
+ public void edit(final MethodCall m) throws CannotCompileException
+ {
+ final CtMethod method;
+ try
+ {
+ method = m.getMethod();
+ }
+ catch(final NotFoundException e)
+ {
+ if(DISPLAY_METHOD_INSTRUMENTATION_FAILURES)
+ {
+ String enclosingClass = "?";
+ try
+ {
+ enclosingClass = m.getEnclosingClass().getName();
+ }
+ catch(final Exception ex)
+ {
+ // Ignore
+ }
+
+ String signature = "?";
+ try
+ {
+ signature = m.getSignature();
+ }
+ catch(final Exception ex)
+ {
+ // Ignore
+ }
+
+ log("Could not find '" + enclosingClass + "->" + signature + "' method; Message: "
+ + e.getMessage());
+ }
+ return;
+ }
+
+ final CtClass declaringClass = method.getDeclaringClass();
+
+ final boolean isThreadDeclaringClass;
+ try
+ {
+ isThreadDeclaringClass = threadClazz != null
+ ? declaringClass.subclassOf(threadClazz)
+ : Thread.class.getName().equals(declaringClass.getName());
+ }
+ catch(final Exception ne)
+ {
+ log("Failed to determine if declaring class is subtype of Thread: " + ne.getMessage());
+ return;
+ }
+ if(isThreadDeclaringClass)
+ {
+ ThreadOriginTransformer.this.replaceThread(m, method.getName(), declaringClass);
+ }
+ }
+ });
+
+ resultingBytes = classUnderTransformation.toBytecode();
+ }
+ catch(final Exception e)
+ {
+ log("Could not instrument "
+ + className
+ + "/"
+ + clazz.getCanonicalName()
+ + ", exception: "
+ + e.getMessage());
+ }
+
+ return resultingBytes;
+ }
+
+ void replaceThread(final MethodCall m, final String methodName, final CtClass declaringClass)
+ throws CannotCompileException
+ {
+ if(methodName.equals("start"))
+ {
+ m.replace("{ "
+ + "System.out.println(\"[TOA] Detected "
+ + declaringClass.getName()
+ + ".start() id: \" + ((Thread)$0).getId() + \" name: \" + "
+ + "((Thread)$0).getName()); "
+ + PRINT_CALLER_THREAD
+ + PRINT_STACK
+ + PROCEED
+ + "} ");
+ }
+ else if(LOG_THREAD_JOINS && methodName.equals("join"))
+ {
+ m.replace("{ "
+ + "System.out.println(\"[TOA] Detected "
+ + declaringClass.getName()
+ + ".join() id: \" + ((Thread)$0).getId() + \" name: \" + "
+ + "((Thread)$0).getName()); "
+ + PRINT_CALLER_THREAD
+ + PROCEED
+ + "} ");
+ }
+ }
+
+ @SuppressWarnings("java:S106")
+ private static void log(final String message)
+ {
+ System.out.println("[TOA] " + message);
+ }
+}
diff --git a/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF b/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..7c3dcc8
--- /dev/null
+++ b/thread-origin-agent/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Premain-Class: software.xdev.tools.threadoriginagent.ThreadOriginTransformer
+Can-Retransform-Classes: true