You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HotSwapAgent.java 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Javassist, a Java-bytecode translator toolkit.
  3. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. Alternatively, the contents of this file may be used under
  8. * the terms of the GNU Lesser General Public License Version 2.1 or later,
  9. * or the Apache License Version 2.0.
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. * for the specific language governing rights and limitations under the
  14. * License.
  15. */
  16. package javassist.util;
  17. import java.io.File;
  18. import java.io.FileOutputStream;
  19. import java.io.IOException;
  20. import java.lang.instrument.ClassDefinition;
  21. import java.lang.instrument.Instrumentation;
  22. import java.lang.instrument.UnmodifiableClassException;
  23. import java.util.jar.Attributes;
  24. import java.util.jar.JarEntry;
  25. import java.util.jar.JarOutputStream;
  26. import java.util.jar.Manifest;
  27. import java.lang.management.ManagementFactory;
  28. import com.sun.tools.attach.VirtualMachine;
  29. import javassist.CannotCompileException;
  30. import javassist.ClassPool;
  31. import javassist.CtClass;
  32. import javassist.NotFoundException;
  33. /**
  34. * A utility class for dynamically adding a new method
  35. * or modifying an existing method body.
  36. * This class provides {@link #redefine(Class, CtClass)}
  37. * and {@link #redefine(Class[], CtClass[])}, which replace the
  38. * existing class definition with a new one.
  39. * These methods perform the replacement by
  40. * {@code java.lang.instrument.Instrumentation}. For details
  41. * of acceptable modification,
  42. * see the {@code Instrumentation} interface.
  43. *
  44. * <p>Before calling the {@code redefine} methods, the hotswap agent
  45. * has to be deployed.</p>
  46. *
  47. * <p>To create a hotswap agent, run {@link #createAgentJarFile(String)}.
  48. * For example, the following command creates an agent file named {@code hotswap.jar}.
  49. *
  50. * <pre>
  51. * $ jshell --class-path javassist.jar
  52. * jshell&gt; javassist.util.HotSwapAgent.createAgentJarFile("hotswap.jar")
  53. * </pre>
  54. *
  55. * <p>Then, run the JVM with the VM argument {@code -javaagent:hotswap.jar}
  56. * to deploy the hotswap agent.
  57. * </p>
  58. *
  59. * <p>If the {@code -javaagent} option is not given to the JVM, {@code HotSwapAgent}
  60. * attempts to automatically create and start the hotswap agent on demand.
  61. * This automated deployment may fail. If it fails, manually create the hotswap agent
  62. * and deploy it by {@code -javaagent}.</p>
  63. *
  64. * <p>The {@code HotSwapAgent} requires {@code tools.jar} as well as {@code javassist.jar}.</p>
  65. *
  66. * <p>The idea of this class was given by <a href="https://github.com/alugowski">Adam Lugowski</a>.
  67. * Shigeru Chiba wrote this class by referring
  68. * to his <a href="https://github.com/turn/RedefineClassAgent">{@code RedefineClassAgent}</a>.
  69. * For details, see <a href="https://github.com/jboss-javassist/javassist/issues/119">this discussion</a>.
  70. * </p>
  71. *
  72. * @see #redefine(Class, CtClass)
  73. * @see #redefine(Class[], CtClass[])
  74. * @since 3.22
  75. */
  76. public class HotSwapAgent {
  77. private static Instrumentation instrumentation = null;
  78. /**
  79. * Obtains the {@code Instrumentation} object.
  80. *
  81. * @return null when it is not available.
  82. */
  83. public Instrumentation instrumentation() { return instrumentation; }
  84. /**
  85. * The entry point invoked when this agent is started by {@code -javaagent}.
  86. */
  87. public static void premain(String agentArgs, Instrumentation inst) throws Throwable {
  88. agentmain(agentArgs, inst);
  89. }
  90. /**
  91. * The entry point invoked when this agent is started after the JVM starts.
  92. */
  93. public static void agentmain(String agentArgs, Instrumentation inst) throws Throwable {
  94. if (!inst.isRedefineClassesSupported())
  95. throw new RuntimeException("this JVM does not support redefinition of classes");
  96. instrumentation = inst;
  97. }
  98. /**
  99. * Redefines a class.
  100. */
  101. public static void redefine(Class oldClass, CtClass newClass)
  102. throws NotFoundException, IOException, CannotCompileException
  103. {
  104. Class[] old = { oldClass };
  105. CtClass[] newClasses = { newClass };
  106. redefine(old, newClasses);
  107. }
  108. /**
  109. * Redefines classes.
  110. */
  111. public static void redefine(Class[] oldClasses, CtClass[] newClasses)
  112. throws NotFoundException, IOException, CannotCompileException
  113. {
  114. startAgent();
  115. ClassDefinition[] defs = new ClassDefinition[oldClasses.length];
  116. for (int i = 0; i < oldClasses.length; i++)
  117. defs[i] = new ClassDefinition(oldClasses[i], newClasses[i].toBytecode());
  118. try {
  119. instrumentation.redefineClasses(defs);
  120. }
  121. catch (ClassNotFoundException e) {
  122. throw new NotFoundException(e.getMessage(), e);
  123. }
  124. catch (UnmodifiableClassException e) {
  125. throw new CannotCompileException(e.getMessage(), e);
  126. }
  127. }
  128. /**
  129. * Ensures that the agent is ready.
  130. * It attempts to dynamically start the agent if necessary.
  131. */
  132. private static void startAgent() throws NotFoundException {
  133. if (instrumentation != null)
  134. return;
  135. try {
  136. File agentJar = createJarFile();
  137. String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
  138. String pid = nameOfRunningVM.substring(0, nameOfRunningVM.indexOf('@'));
  139. VirtualMachine vm = VirtualMachine.attach(pid);
  140. vm.loadAgent(agentJar.getAbsolutePath(), null);
  141. vm.detach();
  142. }
  143. catch (Exception e) {
  144. throw new NotFoundException("hotswap agent", e);
  145. }
  146. for (int sec = 0; sec < 10 /* sec */; sec++) {
  147. if (instrumentation != null)
  148. return;
  149. try {
  150. Thread.sleep(1000);
  151. }
  152. catch (InterruptedException e) {
  153. Thread.currentThread().interrupt();
  154. break;
  155. }
  156. }
  157. throw new NotFoundException("hotswap agent (timeout)");
  158. }
  159. /**
  160. * Creates an agent file for using {@code HotSwapAgent}.
  161. */
  162. public static File createAgentJarFile(String fileName)
  163. throws IOException, CannotCompileException, NotFoundException
  164. {
  165. return createJarFile(new File(fileName));
  166. }
  167. private static File createJarFile()
  168. throws IOException, CannotCompileException, NotFoundException
  169. {
  170. File jar = File.createTempFile("agent", ".jar");
  171. jar.deleteOnExit();
  172. return createJarFile(jar);
  173. }
  174. private static File createJarFile(File jar)
  175. throws IOException, CannotCompileException, NotFoundException
  176. {
  177. Manifest manifest = new Manifest();
  178. Attributes attrs = manifest.getMainAttributes();
  179. attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
  180. attrs.put(new Attributes.Name("Premain-Class"), HotSwapAgent.class.getName());
  181. attrs.put(new Attributes.Name("Agent-Class"), HotSwapAgent.class.getName());
  182. attrs.put(new Attributes.Name("Can-Retransform-Classes"), "true");
  183. attrs.put(new Attributes.Name("Can-Redefine-Classes"), "true");
  184. JarOutputStream jos = null;
  185. try {
  186. jos = new JarOutputStream(new FileOutputStream(jar), manifest);
  187. String cname = HotSwapAgent.class.getName();
  188. JarEntry e = new JarEntry(cname.replace('.', '/') + ".class");
  189. jos.putNextEntry(e);
  190. ClassPool pool = ClassPool.getDefault();
  191. CtClass clazz = pool.get(cname);
  192. jos.write(clazz.toBytecode());
  193. jos.closeEntry();
  194. }
  195. finally {
  196. if (jos != null)
  197. jos.close();
  198. }
  199. return jar;
  200. }
  201. }