Subject: Fix compilation issues with musl libc Upstream: No Author: The portola team at https://openjdk.java.net/projects/portola/ With modifications by Simon Frankenberger This giant patch makes it possible to compile openjdk with musl libc. The base was taken from a diff with the portola project and adjusted for latest musl libc. --- old/make/ReleaseFile.gmk +++ new/make/ReleaseFile.gmk @@ -53,6 +53,7 @@ $(call info-file-item, "JAVA_VERSION_DATE", "$(VERSION_DATE)") $(call info-file-item, "OS_NAME", "$(RELEASE_FILE_OS_NAME)") $(call info-file-item, "OS_ARCH", "$(RELEASE_FILE_OS_ARCH)") + $(call info-file-item, "LIBC", "musl") endef # Param 1 - The file containing the MODULES list --- old/make/autoconf/build-aux/config.guess +++ new/make/autoconf/build-aux/config.guess @@ -30,6 +30,17 @@ DIR=`dirname $0` OUT=`. $DIR/autoconf-config.guess` +# config.guess doesn't identify systems running the musl C library, and will +# instead return a string with a -gnu suffix. This block detects musl and +# modifies the string to have a -musl suffix instead. +echo $OUT | grep -- -linux- > /dev/null 2> /dev/null +if test $? = 0; then + ldd_version=`ldd --version 2>&1 | head -1 | cut -f1 -d' '` + if [ x"${ldd_version}" = x"musl" ]; then + OUT=`echo $OUT | sed 's/-gnu/-musl/'` + fi +fi + # Test and fix solaris on x86_64 echo $OUT | grep i386-pc-solaris > /dev/null 2> /dev/null if test $? = 0; then --- old/make/hotspot/lib/CompileJvm.gmk +++ new/make/hotspot/lib/CompileJvm.gmk @@ -79,6 +79,7 @@ -DHOTSPOT_BUILD_USER='"$(USERNAME)"' \ -DHOTSPOT_VM_DISTRO='"$(HOTSPOT_VM_DISTRO)"' \ -DCPU='"$(OPENJDK_TARGET_CPU_VM_VERSION)"' \ + -DLIBC='"musl"' \ # ################################################################################ --- old/make/lib/CoreLibraries.gmk +++ new/make/lib/CoreLibraries.gmk @@ -219,6 +219,7 @@ endif LIBJLI_CFLAGS += $(LIBZ_CFLAGS) +LIBJLI_CFLAGS += -DLIBC=\"musl\" ifneq ($(USE_EXTERNAL_LIBZ), true) LIBJLI_EXTRA_FILES += \ --- old/src/hotspot/os/linux/os_linux.cpp +++ new/src/hotspot/os/linux/os_linux.cpp @@ -101,7 +101,6 @@ # include # include # include -# include # include # include # include @@ -594,6 +593,11 @@ // detecting pthread library void os::Linux::libpthread_init() { +#if !defined(__GLIBC__) && !defined(__UCLIBC__) + // Hard code Alpine Linux supported musl compatible settings + os::Linux::set_glibc_version("glibc 2.9"); + os::Linux::set_libpthread_version("NPTL"); +#else // Save glibc and pthread version strings. #if !defined(_CS_GNU_LIBC_VERSION) || \ !defined(_CS_GNU_LIBPTHREAD_VERSION) @@ -611,6 +615,7 @@ str = (char *)malloc(n, mtInternal); confstr(_CS_GNU_LIBPTHREAD_VERSION, str, n); os::Linux::set_libpthread_version(str); +#endif } ///////////////////////////////////////////////////////////////////////////// @@ -3109,20 +3114,36 @@ extern "C" JNIEXPORT void numa_warn(int number, char *where, ...) { } extern "C" JNIEXPORT void numa_error(char *where) { } +static void* dlvsym_if_available(void* handle, const char* name, const char* version) { + typedef void* (*dlvsym_func_type)(void* handle, const char* name, const char* version); + static dlvsym_func_type dlvsym_func; + static bool initialized = false; + + if (!initialized) { + dlvsym_func = (dlvsym_func_type)dlsym(RTLD_NEXT, "dlvsym"); + initialized = true; + } + + if (dlvsym_func != NULL) { + void *f = dlvsym_func(handle, name, version); + if (f != NULL) { + return f; + } + } + + return dlsym(handle, name); +} + // Handle request to load libnuma symbol version 1.1 (API v1). If it fails // load symbol from base version instead. void* os::Linux::libnuma_dlsym(void* handle, const char *name) { - void *f = dlvsym(handle, name, "libnuma_1.1"); - if (f == NULL) { - f = dlsym(handle, name); - } - return f; + return dlvsym_if_available(handle, name, "libnuma_1.1"); } // Handle request to load libnuma symbol version 1.2 (API v2) only. // Return NULL if the symbol is not defined in this particular version. void* os::Linux::libnuma_v2_dlsym(void* handle, const char* name) { - return dlvsym(handle, name, "libnuma_1.2"); + return dlvsym_if_available(handle, name, "libnuma_1.2"); } bool os::Linux::libnuma_init() { --- old/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp +++ new/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp @@ -75,9 +75,6 @@ # include # include # include -#ifndef AMD64 -# include -#endif #ifdef AMD64 #define REG_SP REG_RSP --- old/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ new/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -1144,7 +1144,7 @@ static ScratchBlock *removeSmallestScratch(ScratchBlock **prev_ptr) { bool first = true; size_t min_size = 0; // "first" makes this conceptually infinite. - ScratchBlock **smallest_ptr, *smallest; + ScratchBlock **smallest_ptr = NULL, *smallest; ScratchBlock *cur = *prev_ptr; while (cur) { assert(*prev_ptr == cur, "just checking"); --- old/src/hotspot/share/utilities/globalDefinitions_gcc.hpp +++ new/src/hotspot/share/utilities/globalDefinitions_gcc.hpp @@ -204,7 +204,7 @@ #elif defined(__APPLE__) inline int g_isnan(double f) { return isnan(f); } #elif defined(LINUX) || defined(_ALLBSD_SOURCE) -inline int g_isnan(float f) { return isnanf(f); } +inline int g_isnan(float f) { return isnan(f); } inline int g_isnan(double f) { return isnan(f); } #else #error "missing platform-specific definition here" --- old/src/java.base/linux/native/libnet/linux_close.c +++ new/src/java.base/linux/native/libnet/linux_close.c @@ -60,7 +60,7 @@ /* * Signal to unblock thread */ -static int sigWakeup = (__SIGRTMAX - 2); +static int sigWakeup; /* * fdTable holds one entry per file descriptor, up to a certain @@ -149,6 +149,7 @@ /* * Setup the signal handler */ + sigWakeup = SIGRTMAX - 2; sa.sa_handler = sig_wakeup; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); --- old/src/java.base/unix/native/libjava/childproc.c +++ new/src/java.base/unix/native/libjava/childproc.c @@ -238,7 +238,13 @@ { if (envp == NULL || (char **) envp == environ) { execvp(file, (char **) argv); - return; + // ENOEXEC indicates that the file header was not recognized. The musl C + // library does not implement the fallback to /bin/sh for that case, so fall + // through to the code below which implements that fallback using + // execve_with_shell_fallback. + if (errno != ENOEXEC) { + return; + } } if (*file == '\0') { --- old/src/java.base/unix/native/libjava/jdk_util_md.h +++ new/src/java.base/unix/native/libjava/jdk_util_md.h @@ -37,7 +37,7 @@ #define ISNAND(d) isnan(d) #elif defined(__linux__) || defined(_ALLBSD_SOURCE) #include -#define ISNANF(f) isnanf(f) +#define ISNANF(f) isnan(f) #define ISNAND(d) isnan(d) #elif defined(_AIX) #include --- old/src/java.base/unix/native/libjli/java_md_solinux.c +++ new/src/java.base/unix/native/libjli/java_md_solinux.c @@ -236,6 +236,39 @@ char *dmllp = NULL; char *p; /* a utility pointer */ +#ifdef __linux +#ifndef LIBC +#error "LIBC not set" +#endif + + if (strcmp(LIBC, "musl") == 0) { + /* + * The musl library loader requires LD_LIBRARY_PATH to be set in + * order to correctly resolve the dependency libjava.so has on libjvm.so. + * + * Specifically, it differs from glibc in the sense that even if + * libjvm.so has already been loaded it will not be considered a + * candidate for resolving the dependency unless the *full* path + * of the already loaded library matches the dependency being loaded. + * + * libjvm.so is being loaded by the launcher using a long path to + * dlopen, not just the basename of the library. Typically this + * is something like "../lib/server/libjvm.so". However, if/when + * libjvm.so later tries to dlopen libjava.so (which it does in + * order to get access to a few functions implemented in + * libjava.so) the musl loader will, as part of loading + * dependent libraries, try to load libjvm.so using only its + * basename "libjvm.so". Since this does not match the longer + * path path it was first loaded with, the already loaded + * library is not considered a candidate, and the loader will + * instead look for libjvm.so elsewhere. If it's not in + * LD_LIBRARY_PATH the dependency load will fail, and libjava.so + * will therefore fail as well. + */ + return JNI_TRUE; + } +#endif + #ifdef AIX /* We always have to set the LIBPATH on AIX because ld doesn't support $ORIGIN. */ return JNI_TRUE; --- old/src/java.base/unix/native/libnio/ch/NativeThread.c +++ new/src/java.base/unix/native/libnio/ch/NativeThread.c @@ -36,7 +36,7 @@ #ifdef __linux__ #include /* Also defined in net/linux_close.c */ - #define INTERRUPT_SIGNAL (__SIGRTMAX - 2) + #define INTERRUPT_SIGNAL (SIGRTMAX - 2) #elif _AIX #include /* Also defined in net/aix_close.c */ --- old/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c +++ new/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c @@ -27,9 +27,6 @@ #include #include #include -#ifdef __linux__ -#include -#endif #include #include @@ -783,26 +780,6 @@ } return ret; } - -#ifdef __linux__ -void print_stack(void) -{ - void *array[10]; - size_t size; - char **strings; - size_t i; - - size = backtrace (array, 10); - strings = backtrace_symbols (array, size); - - fprintf (stderr, "Obtained %zd stack frames.\n", size); - - for (i = 0; i < size; i++) - fprintf (stderr, "%s\n", strings[i]); - - free (strings); -} -#endif Window get_xawt_root_shell(JNIEnv *env) { static jclass classXRootWindow = NULL; --- old/src/jdk.jdwp.agent/share/native/libjdwp/util.h +++ new/src/jdk.jdwp.agent/share/native/libjdwp/util.h @@ -35,15 +35,15 @@ #ifdef DEBUG /* Just to make sure these interfaces are not used here. */ #undef free - #define free(p) Do not use this interface. + #define free do_not_use_this_interface_free #undef malloc - #define malloc(p) Do not use this interface. + #define malloc do_not_use_this_interface_malloc #undef calloc - #define calloc(p) Do not use this interface. + #define calloc do_not_use_this_interface_calloc #undef realloc - #define realloc(p) Do not use this interface. + #define realloc do_not_use_this_interface_realloc #undef strdup - #define strdup(p) Do not use this interface. + #define strdup do_not_use_this_interface_strdup #endif #include "log_messages.h" --- old/test/hotspot/jtreg/runtime/StackGuardPages/exeinvoke.c +++ new/test/hotspot/jtreg/runtime/StackGuardPages/exeinvoke.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -91,6 +92,20 @@ } } +int get_java_stacksize () { + size_t stacksize; + pthread_attr_t attr; + JDK1_1InitArgs jdk_args; + + jdk_args.version = JNI_VERSION_1_1; + JNI_GetDefaultJavaVMInitArgs(&jdk_args); + if (jdk_args.javaStackSize <= 0) { + fprintf(stderr, "Test ERROR. Can't get a valid value for the default stacksize.\n"); + exit(7); + } + return jdk_args.javaStackSize; +} + void *run_java_overflow (void *p) { JNIEnv *env; jclass class_id; @@ -258,13 +273,19 @@ exit(7); } + int stack_size = get_java_stacksize(); pthread_t thr; + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + pthread_attr_setstacksize(&thread_attr, stack_size); + if (argc > 1 && strcmp(argv[1], "test_java_overflow") == 0) { printf("\nTesting JAVA_OVERFLOW\n"); printf("Testing stack guard page behaviour for other thread\n"); - pthread_create (&thr, NULL, run_java_overflow, NULL); + + pthread_create (&thr, &thread_attr, run_java_overflow, NULL); pthread_join (thr, NULL); printf("Testing stack guard page behaviour for initial thread\n"); @@ -277,7 +298,7 @@ printf("\nTesting NATIVE_OVERFLOW\n"); printf("Testing stack guard page behaviour for other thread\n"); - pthread_create (&thr, NULL, run_native_overflow, NULL); + pthread_create (&thr, &thread_attr, run_native_overflow, NULL); pthread_join (thr, NULL); printf("Testing stack guard page behaviour for initial thread\n"); --- old/test/jdk/java/lang/ProcessBuilder/Basic.java +++ new/test/jdk/java/lang/ProcessBuilder/Basic.java @@ -400,8 +400,8 @@ if (failed != 0) throw new Error("null PATH"); } else if (action.equals("PATH search algorithm")) { equal(System.getenv("PATH"), "dir1:dir2:"); - check(new File("/bin/true").exists()); - check(new File("/bin/false").exists()); + check(new File(TrueExe.path()).exists()); + check(new File(FalseExe.path()).exists()); String[] cmd = {"prog"}; ProcessBuilder pb1 = new ProcessBuilder(cmd); ProcessBuilder pb2 = new ProcessBuilder(cmd); @@ -442,13 +442,13 @@ checkPermissionDenied(pb); // continue searching if EACCES - copy("/bin/true", "dir2/prog"); + copy(TrueExe.path(), "dir2/prog"); equal(run(pb).exitValue(), True.exitValue()); new File("dir1/prog").delete(); new File("dir2/prog").delete(); new File("dir2/prog").mkdirs(); - copy("/bin/true", "dir1/prog"); + copy(TrueExe.path(), "dir1/prog"); equal(run(pb).exitValue(), True.exitValue()); // Check empty PATH component means current directory. @@ -464,10 +464,10 @@ pb.command(command); File prog = new File("./prog"); // "Normal" binaries - copy("/bin/true", "./prog"); + copy(TrueExe.path(), "./prog"); equal(run(pb).exitValue(), True.exitValue()); - copy("/bin/false", "./prog"); + copy(FalseExe.path(), "./prog"); equal(run(pb).exitValue(), False.exitValue()); prog.delete(); @@ -522,12 +522,12 @@ new File("dir2/prog").delete(); new File("prog").delete(); new File("dir3").mkdirs(); - copy("/bin/true", "dir1/prog"); - copy("/bin/false", "dir3/prog"); + copy(TrueExe.path(), "dir1/prog"); + copy(FalseExe.path(), "dir3/prog"); pb.environment().put("PATH","dir3"); equal(run(pb).exitValue(), True.exitValue()); - copy("/bin/true", "dir3/prog"); - copy("/bin/false", "dir1/prog"); + copy(TrueExe.path(), "dir3/prog"); + copy(FalseExe.path(), "dir1/prog"); equal(run(pb).exitValue(), False.exitValue()); } finally { @@ -624,6 +624,13 @@ new File("/bin/false").exists()); } + static class BusyBox { + public static boolean is() { return is; } + private static final boolean is = + (! Windows.is() && + new File("/bin/busybox").exists()); + } + static class UnicodeOS { public static boolean is() { return is; } private static final String osName = System.getProperty("os.name"); @@ -662,6 +669,45 @@ } } + // On alpine linux, /bin/true and /bin/false are just links to /bin/busybox. + // Some tests copy /bin/true and /bin/false to files with a different filename. + // However, copying the busbox executable into a file with a different name + // won't result in the expected return codes. As workaround, we create + // executable files that can be copied and produce the exepected return + // values. We use this workaround, if we find the busybox executable. + + private static class TrueExe { + public static String path() { return path; } + private static final String path = path0(); + private static String path0(){ + if (!BusyBox.is()) { + return "/bin/true"; + } + else { + File trueExe = new File("true"); + setFileContents(trueExe, "#!/bin/true\n"); + trueExe.setExecutable(true); + return trueExe.getAbsolutePath(); + } + } + } + + private static class FalseExe { + public static String path() { return path; } + private static final String path = path0(); + private static String path0(){ + if (!BusyBox.is()) { + return "/bin/false"; + } + else { + File falseExe = new File("false"); + setFileContents(falseExe, "#!/bin/false\n"); + falseExe.setExecutable(true); + return falseExe.getAbsolutePath(); + } + } + } + static class EnglishUnix { private static final Boolean is = (! Windows.is() && isEnglish("LANG") && isEnglish("LC_ALL")); @@ -1965,7 +2011,7 @@ //---------------------------------------------------------------- try { new File("suBdiR").mkdirs(); - copy("/bin/true", "suBdiR/unliKely"); + copy(TrueExe.path(), "suBdiR/unliKely"); final ProcessBuilder pb = new ProcessBuilder(new String[]{"unliKely"}); pb.environment().put("PATH", "suBdiR"); --- old/test/jdk/java/lang/ProcessHandle/InfoTest.java +++ new/test/jdk/java/lang/ProcessHandle/InfoTest.java @@ -298,7 +298,14 @@ } if (info.command().isPresent()) { String command = info.command().get(); - String expected = Platform.isWindows() ? "sleep.exe" : "sleep"; + String expected = "sleep"; + if (Platform.isWindows()) { + expected = "sleep.exe"; + } else if (new File("/bin/busybox").exists()) { + // With busybox sleep is just a sym link to busybox. + // The busbox executable is seen as ProcessHandle.Info command. + expected = "busybox"; + } Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" + expected + "\', actual: " + command);