|
5 | 5 | import com.sun.tools.attach.AttachNotSupportedException; |
6 | 6 | import com.sun.tools.attach.VirtualMachine; |
7 | 7 | import com.sun.tools.attach.VirtualMachineDescriptor; |
| 8 | +import java.io.File; |
8 | 9 | import java.io.IOException; |
9 | 10 |
|
10 | 11 | /** |
|
21 | 22 | public class AttachHelper { |
22 | 23 | public static void handle(String jarPath, String pid, String attachOptions) |
23 | 24 | throws FailureMessageException { |
| 25 | + if (isWindows()) { |
| 26 | + loadAttachLibrary(); |
| 27 | + } |
24 | 28 | if (pid.equals("list")) { |
25 | 29 | System.out.print(AttachHelper.list()); |
26 | 30 | } else { |
@@ -83,4 +87,74 @@ private static boolean pidExists(String pid) { |
83 | 87 | } |
84 | 88 | return false; |
85 | 89 | } |
| 90 | + |
| 91 | + private static boolean isWindows() { |
| 92 | + return System.getProperty("os.name").startsWith("Windows"); |
| 93 | + } |
| 94 | + |
| 95 | + |
| 96 | + // The Windows library-loading fix lives here rather than in AgentAttach because native |
| 97 | + // library ownership is classloader-scoped. The JVM keeps a global set of loaded library |
| 98 | + // names; if a library is already owned by one classloader, any other classloader that |
| 99 | + // attempts to load it by name receives UnsatisfiedLinkError("already loaded in another |
| 100 | + // classloader") — unconditionally, with no exception for parent/child relationships. |
| 101 | + // See: NativeLibraries.loadLibrary() in OpenJDK |
| 102 | + // src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java |
| 103 | + // (Holder.loadedLibraryNames global set, checked before every load attempt) |
| 104 | + // and the JNI spec: https://docs.oracle.com/en/java/javase/21/docs/specs/jni/design.html |
| 105 | + // ("the VM internally maintains a list of loaded native libraries for each class loader") |
| 106 | + // |
| 107 | + // AttachHelper is loaded inside URLClassLoader(cp, null). Moving System.load() to |
| 108 | + // AgentAttach (app classloader) would register "attach" there; when |
| 109 | + // WindowsAttachProvider's static initializer calls System.loadLibrary("attach") from |
| 110 | + // within the URLClassLoader it would hit the global name check and fail. Keeping |
| 111 | + // loadAttachLibrary() in AttachHelper ensures both the pre-load and the SPI-triggered |
| 112 | + // load share the same classloader. |
| 113 | + private static void loadAttachLibrary() throws FailureMessageException { |
| 114 | + try { |
| 115 | + System.loadLibrary("attach"); |
| 116 | + // All good — system is set up properly. |
| 117 | + } catch (UnsatisfiedLinkError e) { |
| 118 | + // attach.dll not on the default search path; try well-known fallback locations. |
| 119 | + // This can happen when running with a standalone JRE whose bin/ has no attach.dll |
| 120 | + // while JAVA_HOME points to a JDK that does have it under jre/bin/. |
| 121 | + if (!tryLoadLibrary("jre/bin/attach.dll")) { |
| 122 | + throw new FailureMessageException( |
| 123 | + "Failed loading attach provider." |
| 124 | + + " Make sure you are running with a JDK java executable." |
| 125 | + + " Alternatively locate 'attach.dll' on your system," |
| 126 | + + " typically found in '<jdk home>/jre/bin' for Oracle JDK," |
| 127 | + + " and pass the path at startup as:", |
| 128 | + " java -Djava.library.path=\"<jdk home>/jre/bin\"" |
| 129 | + + " -jar extract-tls-secrets.jar"); |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // Duplicated from AgentAttach — cannot be shared across the ClassLoader boundary. |
| 135 | + private static File getJavaHome() throws FailureMessageException { |
| 136 | + String javaHomeEnv = System.getenv("JAVA_HOME"); |
| 137 | + if (javaHomeEnv != null) { |
| 138 | + return new File(javaHomeEnv); |
| 139 | + } |
| 140 | + throw new FailureMessageException( |
| 141 | + "No JAVA_HOME environment variable found." |
| 142 | + + " Must point to a local JDK installation."); |
| 143 | + } |
| 144 | + |
| 145 | + private static boolean tryLoadLibrary(String relativePath) throws FailureMessageException { |
| 146 | + File javaHome = getJavaHome(); |
| 147 | + File lib = new File(javaHome, relativePath); |
| 148 | + if (!lib.exists()) { |
| 149 | + return false; |
| 150 | + } |
| 151 | + try { |
| 152 | + System.load(lib.getAbsolutePath()); |
| 153 | + } catch (UnsatisfiedLinkError e) { |
| 154 | + return false; |
| 155 | + } |
| 156 | + // Once loaded by absolute path the library is registered under the name "attach", |
| 157 | + // so any subsequent System.loadLibrary("attach") calls will find it already loaded. |
| 158 | + return true; |
| 159 | + } |
86 | 160 | } |
0 commit comments