Try to monkey-patch library id when shading is used and we are on Mac… (#8210)

* Try to monkey-patch library id when shading is used and we are on MacOS / OSX.

Motivation:

ea4c315b45 did ensure we support using multiple versions of the same shaded native library but the user still needed to run install_name_tool -id on MacOS to ensure the ID is unique.
This is kind of error prone and also means that the shading itself would need to be done on MacOS / OSX.

This is related to https://github.com/netty/netty/issues/7272.

Modifications:

- Monkey patch the shaded native lib on MacOS to ensure the id is unique while unpacking it to the tempory location.

Result:

Easier way of using shaded native libs in netty.
This commit is contained in:
Norman Maurer 2018-08-23 11:07:09 +02:00 committed by GitHub
parent bbb6e126b1
commit 2bb9f64e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 26 deletions

View File

@ -15,6 +15,7 @@
*/
package io.netty.util.internal;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -47,6 +48,11 @@ public final class NativeLibraryLoader {
private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
private static final File WORKDIR;
private static final boolean DELETE_NATIVE_LIB_AFTER_LOADING;
private static final boolean TRY_TO_PATCH_SHADED_ID;
// Just use a-Z and numbers as valid ID bytes.
private static final byte[] UNIQUE_ID_BYTES =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(CharsetUtil.US_ASCII);
static {
String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
@ -69,6 +75,11 @@ public final class NativeLibraryLoader {
DELETE_NATIVE_LIB_AFTER_LOADING = SystemPropertyUtil.getBoolean(
"io.netty.native.deleteLibAfterLoading", true);
logger.debug("-Dio.netty.native.deleteLibAfterLoading: {}", DELETE_NATIVE_LIB_AFTER_LOADING);
TRY_TO_PATCH_SHADED_ID = SystemPropertyUtil.getBoolean(
"io.netty.native.tryPatchShadedId", true);
logger.debug("-Dio.netty.native.tryPatchShadedId: {}", TRY_TO_PATCH_SHADED_ID);
}
/**
@ -117,7 +128,8 @@ public final class NativeLibraryLoader {
*/
public static void load(String originalName, ClassLoader loader) {
// Adjust expected name to support shading of native libraries.
String name = calculatePackagePrefix().replace('.', '_') + originalName;
String packagePrefix = calculatePackagePrefix().replace('.', '_');
String name = packagePrefix + originalName;
List<Throwable> suppressed = new ArrayList<Throwable>();
try {
// first try to load from java.library.path
@ -174,8 +186,25 @@ public final class NativeLibraryLoader {
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
if (TRY_TO_PATCH_SHADED_ID && PlatformDependent.isOsx() && !packagePrefix.isEmpty()) {
// We read the whole native lib into memory to make it easier to monkey-patch the id.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(in.available());
while ((length = in.read(buffer)) > 0) {
byteArrayOutputStream.write(buffer, 0, length);
}
byteArrayOutputStream.flush();
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
// Try to patch the library id.
patchShadedLibraryId(bytes, originalName, name);
out.write(bytes);
} else {
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
out.flush();
@ -183,7 +212,6 @@ public final class NativeLibraryLoader {
// because otherwise Windows will refuse to load it when it's in use by other process.
closeQuietly(out);
out = null;
loadLibrary(loader, tmpFile.getPath(), true);
} catch (UnsatisfiedLinkError e) {
try {
@ -218,6 +246,51 @@ public final class NativeLibraryLoader {
}
}
/**
* Try to patch shaded library to ensure it uses a unique ID.
*/
private static void patchShadedLibraryId(byte[] bytes, String originalName, String name) {
// Our native libs always have the name as part of their id so we can search for it and replace it
// to make the ID unique if shading is used.
byte[] nameBytes = originalName.getBytes(CharsetUtil.UTF_8);
int idIdx = -1;
// Be aware this is a really raw way of patching a dylib but it does all we need without implementing
// a full mach-o parser and writer. Basically we just replace the the original bytes with some
// random bytes as part of the ID regeneration. The important thing here is that we need to use the same
// length to not corrupt the mach-o header.
outerLoop: for (int i = 0; i < bytes.length && bytes.length - i >= nameBytes.length; i++) {
int idx = i;
for (int j = 0; j < nameBytes.length;) {
if (bytes[idx++] != nameBytes[j++]) {
// Did not match the name, increase the index and try again.
break;
} else if (j == nameBytes.length) {
// We found the index within the id.
idIdx = i;
break outerLoop;
}
}
}
if (idIdx == -1) {
logger.debug("Was not able to find the ID of the shaded native library {}, can't adjust it.", name);
} else {
// We found our ID... now monkey-patch it!
for (int i = 0; i < nameBytes.length; i++) {
// We should only use bytes as replacement that are in our UNIQUE_ID_BYTES array.
bytes[idIdx + i] = UNIQUE_ID_BYTES[PlatformDependent.threadLocalRandom()
.nextInt(UNIQUE_ID_BYTES.length)];
}
if (logger.isDebugEnabled()) {
logger.debug(
"Found the ID of the shaded native library {}. Replacing ID part {} with {}",
name, originalName, new String(bytes, idIdx, nameBytes.length, CharsetUtil.UTF_8));
}
}
}
/**
* Loading the native library into the specified {@link ClassLoader}.
* @param loader - The {@link ClassLoader} where the native library will be loaded into

View File

@ -166,32 +166,10 @@
</unzip>
<copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTransportLib}" />
<copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTransportLib}" />
<exec executable="install_name_tool" failonerror="true" dir="${project.build.directory}/" resolveexecutable="true">
<arg value="-id" />
<arg value="lib${shadingPrefix}_${nativeTransportLib}" />
<arg value="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTransportLib}" />
</exec>
<!-- We need to adjust the ID used on MacOS so we are sure the correct lib is loaded later on -->
<exec executable="install_name_tool" failonerror="true" dir="${project.build.directory}/" resolveexecutable="true">
<arg value="-id" />
<arg value="lib${shadingPrefix2}_${nativeTransportLib}" />
<arg value="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTransportLib}" />
</exec>
<delete file="${classesShadedNativeDir}/lib${nativeTransportLib}" />
<copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTcnativeLib}" />
<copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTcnativeLib}" />
<exec executable="install_name_tool" failonerror="true" dir="${project.build.directory}/" resolveexecutable="true">
<arg value="-id" />
<arg value="lib${shadingPrefix}_${nativeTcnativeLib}" />
<arg value="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTcnativeLib}" />
</exec>
<!-- We need to adjust the ID used on MacOS so we are sure the correct lib is loaded later on -->
<exec executable="install_name_tool" failonerror="true" dir="${project.build.directory}/" resolveexecutable="true">
<arg value="-id" />
<arg value="lib${shadingPrefix2}_${nativeTcnativeLib}" />
<arg value="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTcnativeLib}" />
</exec>
<delete file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" />
<jar destfile="${project.build.directory}/${jarName}" basedir="${classesShadedDir}" />