diff --git a/Makefile b/Makefile index 736d39da..600483d5 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ ifeq ($(OS),Darwin) CXXFLAGS += $(FAT_BINARY_FLAGS) PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG) MERGE=false + SKIP_IN_RELEASE=true endif else CXXFLAGS += -Wl,-z,defs @@ -102,7 +103,8 @@ $(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR) rm -r $(PACKAGE_DIR) $(PACKAGE_NAME).zip: $(PACKAGE_DIR) - codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/$(ASPROF) $(PACKAGE_DIR)/$(LIB_PROFILER) + codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/$(ASPROF) $(PACKAGE_DIR)/$(JFRCONV) $(PACKAGE_DIR)/$(LIB_PROFILER) + cat build/$(CONVERTER_JAR) >> $(PACKAGE_DIR)/$(JFRCONV) ditto -c -k --keepParent $(PACKAGE_DIR) $@ rm -r $(PACKAGE_DIR) @@ -119,14 +121,10 @@ build/$(ASPROF): src/main/* src/jattach/* src/fdtransfer.h $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" -o $@ src/main/*.cpp src/jattach/*.c strip $@ -build/$(JFRCONV): src/launcher/* src/incbin.h $(JAVA_HELPER_CLASSES) build/$(CONVERTER_JAR) - $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -o $@ src/launcher/*.cpp -ldl - -build/$(JFRCONV).exe: src/launcher/* src/incbin.h $(JAVA_HELPER_CLASSES) build/$(CONVERTER_JAR) - mkdir -p build/bin build/gensrc - (echo -n "const unsigned char CLASS_BYTES[] = {" && hexdump -v -e '1/1 "%u,"' src/helper/one/profiler/EmbeddedClassLoader.class && echo "}; const unsigned char CLASS_BYTES_END = {0};") > build/gensrc/CLASS_BYTES.c - (echo -n "const unsigned char CONVERTER_JAR[] = {" && hexdump -v -e '1/1 "%u,"' build/$(CONVERTER_JAR) && echo "}; const unsigned char CONVERTER_JAR_END = {0};") > build/gensrc/CONVERTER_JAR.c - cmd.exe /C cl /O2 /DPROFILER_VERSION=\"$(PROFILER_VERSION)\" src/launcher/*.cpp build/gensrc/*.c /Fo:build/gensrc/ /Fe:$@ +build/$(JFRCONV): src/launcher/* build/$(CONVERTER_JAR) + $(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" -o $@ src/launcher/*.cpp + strip $@ + $(SKIP_IN_RELEASE) cat build/$(CONVERTER_JAR) >> $@ build/$(LIB_PROFILER): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES) ifeq ($(MERGE),true) diff --git a/src/helper/one/profiler/EmbeddedClassLoader.class b/src/helper/one/profiler/EmbeddedClassLoader.class deleted file mode 100644 index 05804aa6..00000000 Binary files a/src/helper/one/profiler/EmbeddedClassLoader.class and /dev/null differ diff --git a/src/helper/one/profiler/EmbeddedClassLoader.java b/src/helper/one/profiler/EmbeddedClassLoader.java deleted file mode 100644 index b72cb69b..00000000 --- a/src/helper/one/profiler/EmbeddedClassLoader.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright The async-profiler authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package one.profiler; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -/** - * Loads classes from a JAR file embedded in a shared library. - */ -public class EmbeddedClassLoader extends ClassLoader { - private final ByteBuffer jar; - private final Map directory; - - public EmbeddedClassLoader(ClassLoader parent, ByteBuffer jar) { - super(parent); - this.jar = jar; - this.directory = new HashMap<>(); - - String specVersion = System.getProperty("java.specification.version"); - int currentVersion = specVersion == null || specVersion.startsWith("1.") ? 8 : Integer.parseInt(specVersion); - - jar.order(ByteOrder.LITTLE_ENDIAN); - int eocd = jar.limit() - 22; - if (jar.getInt(eocd) != 0x06054b50) { - throw new IllegalStateException("EOCD signature not found"); - } - - int chdr = jar.getInt(eocd + 16); - while (jar.getInt(chdr) == 0x02014b50) { - int compressedSize = jar.getInt(chdr + 20); - int uncompressedSize = jar.getInt(chdr + 24); - int fileNameLength = jar.getShort(chdr + 28) & 0xffff; - int extraLength = jar.getInt(chdr + 30); - int fileHeaderStart = jar.getInt(chdr + 42); - - byte[] fileNameBytes = new byte[fileNameLength]; - ((Buffer) jar).position(chdr + 46); - jar.get(fileNameBytes); - - String fileName = new String(fileNameBytes, StandardCharsets.UTF_8); - if (!fileName.endsWith("/")) { - int[] entry = {fileHeaderStart, compressedSize, uncompressedSize}; - if (fileName.startsWith("META-INF/versions/")) { - int p = fileName.indexOf('/', 18); - if (p > 0 && Integer.parseInt(fileName.substring(18, p)) <= currentVersion) { - directory.put(fileName.substring(p + 1), entry); - } - } else { - directory.putIfAbsent(fileName, entry); - } - } - - chdr += 46 + fileNameLength + (extraLength & 0xffff) + (extraLength >>> 16); - } - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - byte[] data = unzip(name.replace('.', '/').concat(".class")); - if (data == null) { - throw new ClassNotFoundException(name); - } - return defineClass(name, data, 0, data.length); - } - - @Override - public InputStream getResourceAsStream(String name) { - byte[] data = unzip(name.startsWith("/") ? name.substring(1) : name); - if (data == null) { - return super.getResourceAsStream(name); - } - return new ByteArrayInputStream(data); - } - - private byte[] unzip(String name) { - int[] entry = directory.get(name); - if (entry == null) { - return null; - } - - int loc = entry[0]; - if (jar.getInt(loc) != 0x04034b50) { - throw new IllegalStateException("LOC signature not found"); - } - - byte[] compressed = new byte[entry[1]]; - int extraLength = jar.getInt(loc + 26); - ByteBuffer jarCopy = jar.duplicate(); - ((Buffer) jarCopy).position(loc + 30 + (extraLength & 0xffff) + (extraLength >>> 16)); - jarCopy.get(compressed); - - short method = jar.getShort(loc + 8); - if (method == 0) { - return compressed; - } else if (method != 8) { - throw new IllegalStateException("Unsupported compression algorithm"); - } - - byte[] uncompressed = new byte[entry[2]]; - Inflater inf = new Inflater(true); - try { - inf.setInput(compressed); - if (inf.inflate(uncompressed) != uncompressed.length) { - throw new IllegalStateException("Uncompressed size mismatch"); - } - return uncompressed; - } catch (DataFormatException e) { - throw new IllegalStateException("Invalid compressed data"); - } finally { - inf.end(); - } - } - - public static Class loadMainClass(ByteBuffer jar) throws ClassNotFoundException { - EmbeddedClassLoader loader = new EmbeddedClassLoader(EmbeddedClassLoader.class.getClassLoader(), jar); - byte[] manifest = loader.unzip("META-INF/MANIFEST.MF"); - if (manifest == null) { - throw new IllegalStateException("MANIFEST.MF not found"); - } - - String s = new String(manifest, StandardCharsets.UTF_8); - int p = s.indexOf("Main-Class:"); - if (p < 0) { - throw new IllegalStateException("Main-Class attribute not found"); - } - - int q = s.indexOf('\n', p += 11); - String mainClass = (q >= 0 ? s.substring(p, q) : s.substring(p)).trim(); - return loader.findClass(mainClass); - } -} diff --git a/src/launcher/launcher.cpp b/src/launcher/launcher.cpp index 622d6be3..efc3b7b8 100644 --- a/src/launcher/launcher.cpp +++ b/src/launcher/launcher.cpp @@ -3,273 +3,118 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include +#include +#include #include #include #include #include -#include -#include "../incbin.h" +#include -#ifdef _WIN32 -#include -#include + +#ifdef __APPLE__ +# include +# define COMMON_JVM_DIR "/Library/Java/JavaVirtualMachines/" +# define CONTENTS_HOME "/Contents/Home" #else -#include -#include -#include +# define COMMON_JVM_DIR "/usr/lib/jvm/" +# define CONTENTS_HOME "" #endif +#define JAVA_EXE "java" -#define APP_BINARY "jfrconv" static const char VERSION_STRING[] = "JFR converter " PROFILER_VERSION " built on " __DATE__ "\n"; -INCLUDE_HELPER_CLASS(EMBEDDED_CLASS_LOADER, CLASS_BYTES, "one/profiler/EmbeddedClassLoader") -INCBIN(CONVERTER_JAR, "build/jar/jfr-converter.jar") +static char exe_path[PATH_MAX]; +static char java_path[PATH_MAX]; -#if defined(__APPLE__) || defined(_WIN32) -// There is no arch subdirectory in macOS JDK bundles -# define ARCH "" -#elif defined(__x86_64__) -# define ARCH "amd64" -#elif defined(__i386__) -# define ARCH "i386" -#elif defined(__arm__) || defined(__thumb__) -# define ARCH "arm" -#elif defined(__aarch64__) -# define ARCH "aarch64" -#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -# define ARCH "ppc64le" -#elif defined(__riscv) && (__riscv_xlen == 64) -# define ARCH "riscv64" -#elif defined(__loongarch_lp64) -# define ARCH "loongarch64" -#endif - -#if defined(__APPLE__) -# define COMMON_JVM_DIR "/Library/Java/JavaVirtualMachines/" -# define CONTENTS_HOME "/Contents/Home" -# define LIB_DIR "lib" -# define LIBJVM "libjvm.dylib" -# define PATH_SEP ":" -# define SLASH '/' -#elif defined(_WIN32) -# define COMMON_JVM_DIR "C:\\Program Files\\Java\\" -# define CONTENTS_HOME "" -# define LIB_DIR "bin" -# define LIBJVM "jvm.dll" -# define PATH_SEP ";" -# define SLASH '\\' -#else -# define COMMON_JVM_DIR "/usr/lib/jvm/" -# define CONTENTS_HOME "" -# define LIB_DIR "lib" -# define LIBJVM "libjvm.so" -# define PATH_SEP ":" -# define SLASH '/' -#endif - -#ifdef _WIN32 -# define PATH_MAX MAX_PATH -# define dlopen(name, _) LoadLibrary(name) -# define dlsym(lib, name) GetProcAddress((HMODULE)lib, name) -# define realpath(path, buf) _fullpath(buf, path, MAX_PATH) -#endif - - -typedef jint (*JNI_CreateJavaVM_t)(JavaVM **p_vm, void **p_env, void *vm_args); - -static void* load_libjvm(const char* java_home, const char* subdir) { +static bool get_exe_path() { +#ifdef __APPLE__ char buf[PATH_MAX]; - if (snprintf(buf, sizeof(buf), "%s/%s/" LIBJVM, java_home, subdir) >= sizeof(buf)) { - return NULL; - } - - struct stat statbuf; - if (stat(buf, &statbuf) != 0) { - return NULL; - } - - return dlopen(buf, RTLD_NOW); -} - -static void* find_libjvm_at(const char* path, const char* path1 = "", const char* path2 = "") { - char buf[PATH_MAX]; - if (snprintf(buf, sizeof(buf), "%s%s%s", path, path1, path2) >= sizeof(buf)) { - return NULL; - } - - char* java_home = realpath(buf, NULL); - if (java_home == NULL) { - return NULL; - } - - char* p = strrchr(java_home, SLASH); - if (p != NULL) { - *p = 0; // strip /java - } - if ((p = strrchr(java_home, SLASH)) != NULL) { - *p = 0; // strip /bin - } - - void* libjvm; - if ((libjvm = load_libjvm(java_home, LIB_DIR "/server")) == NULL && - (libjvm = load_libjvm(java_home, LIB_DIR "/client")) == NULL && - (libjvm = load_libjvm(java_home, LIB_DIR "/" ARCH "/server")) == NULL && - (libjvm = load_libjvm(java_home, LIB_DIR "/" ARCH "/client")) == NULL && - (libjvm = load_libjvm(java_home, "jre/" LIB_DIR "/" ARCH "/server")) == NULL && - (libjvm = load_libjvm(java_home, "jre/" LIB_DIR "/" ARCH "/client")) == NULL) { - // No libjvm.so found at this path - } - - free(java_home); - return libjvm; -} - -static void* find_libjvm() { - void* libjvm; - char* java_home = getenv("JAVA_HOME"); - if (java_home != NULL && (libjvm = find_libjvm_at(java_home, "/bin/java")) != NULL) { - return libjvm; - } - - char* path = getenv("PATH"); - char* path_copy; - if (path != NULL && (path_copy = strdup(path)) != NULL) { - for (char* java_bin = strtok(path_copy, PATH_SEP); java_bin != NULL; java_bin = strtok(NULL, PATH_SEP)) { - if ((libjvm = find_libjvm_at(java_bin, "/java")) != NULL) { - free(path_copy); - return libjvm; - } - } - free(path_copy); - } - -#ifdef __linux__ - if ((libjvm = find_libjvm_at("/etc/alternatives/java")) != NULL) { - return libjvm; - } -#endif - -#ifdef _WIN32 - WIN32_FIND_DATA entry; - HANDLE dir = FindFirstFile(COMMON_JVM_DIR, &entry); - if (dir != INVALID_HANDLE_VALUE) { - do { - if (entry.cFileName[0] != '.' && (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - if ((libjvm = find_libjvm_at(COMMON_JVM_DIR, entry.cFileName, CONTENTS_HOME "/bin/java")) != NULL) { - CloseHandle(dir); - return libjvm; - } - } - } while (FindNextFile(dir, &entry)); - CloseHandle(dir); - } + uint32_t size = sizeof(buf); + return _NSGetExecutablePath(buf, &size) == 0 && realpath(buf, exe_path) != NULL; #else - DIR* dir = opendir(COMMON_JVM_DIR); - if (dir != NULL) { - struct dirent* entry; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] != '.' && entry->d_type == DT_DIR) { - if ((libjvm = find_libjvm_at(COMMON_JVM_DIR, entry->d_name, CONTENTS_HOME "/bin/java")) != NULL) { - closedir(dir); - return libjvm; - } - } + if (realpath("/proc/self/exe", exe_path) == NULL) { + // realpath() may fail for a path like /proc/[pid]/root/bin/exe + ssize_t size = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (size < 0) { + return false; } - closedir(dir); + exe_path[size] = 0; } + return true; #endif - - return NULL; } -static int print_exception(JavaVM* vm, JNIEnv* env) { - env->ExceptionDescribe(); - vm->DestroyJavaVM(); - return 1; -} +static const char* const* build_cmdline(int argc, char** argv) { + const char** cmd = (const char**)malloc((argc + 5) * sizeof(char*)); + int count = 0; -static int run_jvm(void* libjvm, int argc, char** argv) { - JNI_CreateJavaVM_t JNI_CreateJavaVM = (JNI_CreateJavaVM_t)dlsym(libjvm, "JNI_CreateJavaVM"); - if (JNI_CreateJavaVM == NULL) { - return 1; - } - - JavaVMOption* options = (JavaVMOption*)alloca((argc + 2) * sizeof(JavaVMOption)); - int o_count = 0; - options[o_count++].optionString = (char*)"-Dsun.java.command=" APP_BINARY; - options[o_count++].optionString = (char*)"-Xss2M"; + cmd[count++] = JAVA_EXE; + cmd[count++] = "-Xss2M"; for (; argc > 0; argc--, argv++) { if ((strncmp(*argv, "-D", 2) == 0 || strncmp(*argv, "-X", 2) == 0) && (*argv)[2] || strncmp(*argv, "-agent", 6) == 0) { - options[o_count++].optionString = *argv; + cmd[count++] = *argv; } else if (strncmp(*argv, "-J", 2) == 0) { - options[o_count++].optionString = *argv + 2; + cmd[count++] = *argv + 2; } else { break; } } - JavaVM* vm; - JNIEnv* env; - JavaVMInitArgs args; - args.version = JNI_VERSION_1_6; - args.nOptions = o_count; - args.options = options; - args.ignoreUnrecognized = JNI_TRUE; + cmd[count++] = "-jar"; + cmd[count++] = exe_path; - int res = JNI_CreateJavaVM(&vm, (void**)&env, &args); - if (res != 0) { - return res; + for (; argc > 0; argc--, argv++) { + cmd[count++] = *argv; } - jclass loader = env->DefineClass(EMBEDDED_CLASS_LOADER, NULL, (const jbyte*)CLASS_BYTES, INCBIN_SIZEOF(CLASS_BYTES)); - if (loader == NULL) { - return print_exception(vm, env); + cmd[count] = NULL; + return cmd; +} + +static bool find_java_at(const char* path, const char* path1 = "", const char* path2 = "") { + if (snprintf(java_path, sizeof(java_path), "%s%s%s/" JAVA_EXE, path, path1, path2) >= sizeof(java_path)) { + return false; } - jmethodID load_main = env->GetStaticMethodID(loader, "loadMainClass", "(Ljava/nio/ByteBuffer;)Ljava/lang/Class;"); - if (load_main == NULL) { - return print_exception(vm, env); + struct stat st; + return stat(java_path, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR) != 0; +} + +static void run_java(char* const* cmd) { + // 1. Get java executable from JAVA_HOME + char* java_home = getenv("JAVA_HOME"); + if (java_home != NULL && find_java_at(java_home, "/bin")) { + execv(java_path, cmd); } - jobject jar = env->NewDirectByteBuffer((void*)CONVERTER_JAR, INCBIN_SIZEOF(CONVERTER_JAR)); - if (jar == NULL) { - return print_exception(vm, env); + // 2. Try to find java in PATH + execvp(JAVA_EXE, cmd); + + // 3. Try /etc/alternatives/java + if (find_java_at("/etc/alternatives")) { + execv(java_path, cmd); } - jclass main_class = (jclass)env->CallStaticObjectMethod(loader, load_main, jar); - if (main_class == NULL) { - return print_exception(vm, env); - } - - jmethodID main_method = env->GetStaticMethodID(main_class, "main", "([Ljava/lang/String;)V"); - if (main_method == NULL) { - return print_exception(vm, env); - } - - jobjectArray main_args = env->NewObjectArray(argc, env->FindClass("java/lang/String"), NULL); - if (main_args != NULL) { - for (int i = 0; i < argc; i++) { - env->SetObjectArrayElement(main_args, i, env->NewStringUTF(argv[i])); + // 4. Look for java in the common directory + DIR* dir = opendir(COMMON_JVM_DIR); + if (dir != NULL) { + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] != '.' && entry->d_type == DT_DIR) { + if (find_java_at(COMMON_JVM_DIR, entry->d_name, CONTENTS_HOME "/bin")) { + execv(java_path, cmd); + } + } } + closedir(dir); } - if (env->ExceptionCheck()) { - return print_exception(vm, env); - } - - env->CallStaticVoidMethod(main_class, main_method, main_args); - if (env->ExceptionCheck()) { - return print_exception(vm, env); - } - - vm->DestroyJavaVM(); - return 0; } int main(int argc, char** argv) { @@ -278,11 +123,15 @@ int main(int argc, char** argv) { return 0; } - void* libjvm = find_libjvm(); - if (libjvm == NULL) { - fprintf(stderr, "No JDK found. Set JAVA_HOME or ensure java executable is on the PATH.\n"); + if (!get_exe_path()) { + fprintf(stderr, "Failed to get executable path\n"); return 1; } - return run_jvm(libjvm, argc - 1, argv + 1); + const char* const* cmd = build_cmdline(argc - 1, argv + 1); + run_java((char* const*)cmd); + + // May reach here only if run_java() fails + fprintf(stderr, "No JDK found. Set JAVA_HOME or ensure java executable is on the PATH\n"); + return 1; }