问题产生背景
使用IDE为idea intellij,在SpringBoot项目中需要用到native方法,因此需要依赖so包(Shared Object Library)。考虑把so包放在项目的resource目录下,并通过System.loadLibrary方法加载包。加载时路径配置有两种方法。
- 启动系统通过-D参数指定绝对位置。
- 系统运行时内部代码通过相对位置定位。
2在运行时通过相对位置加载提高灵活性,因此采用2的方式,并将包放在resource目录下。
/xxx/src/main/resources/lib/
├── libXXX.dylib
└── libXXX.so
加载so包代码如下
public class XXXDecoder {
public native String en(String input);
public native String de(String input);
static {
try {
initLibPath();
System.loadLibrary("XXX");
} catch (Exception e) {
logger.error("loadLibraryException", e);
}
}
private static void initLibPath() throws Exception {
String path = XXXDecoder.class.getResource("/").getPath() + "lib";
System.setProperty("java.library.path", path);
final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
sysPathsField.setAccessible(true);
sysPathsField.set(null, null);
}
}
但启动项目后确暴出如下错误,导致无法正常使用。
java.lang.UnsatisfiedLinkError
no suitable image found.
unknown file type, first eight bytes
Caused by: java.lang.UnsatisfiedLinkError: /xxx/target/classes/lib/xxx.so: dlopen(/xxx/target/classes/lib/xxx.so, 1): no suitable image found. Did find:
/xxx/target/classes/lib/xxx.so: unknown file type, first eight bytes: 0xEF 0xBF 0xBD 0xEF 0xBF 0xBD 0xEF 0xBF
/xxx/target/classes/lib/xxx.so: unknown file type, first eight bytes: 0xEF 0xBF 0xBD 0xEF 0xBF 0xBD 0xEF 0xBF
问题分析
前文提到加载有两种方式,其中一种可以通过-D参数指定绝对置设置。于是尝试指定绝对位置看是否会产生问题。通过两种方式对比来分析问题。
通过-D指定参数为resource目录下的位置。随后启动系统,发现一切正常。
-Djava.library.path=/xxx/src/main/resources/lib/xxx.so
随后替换参数为target目录的so包位置,并启动依然报 no suitable image found.unknown file type, first eight bytes:
-Djava.library.path=/xxx/target/classes/lib/xxx.so
通过第一次对比,可以复现出正常和异常两种情况。接着要搞清楚unknown file type, first eight bytes:到底什么含义。根据异常提示猜测可能和文件格式虽坏有关系。由于so采用的是ELF格式[1],通常查看ELF需要通过命令readelf命令。
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 200 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 8
Section header string table index: 5
接着通过readelf查看两个so到底有什么差异。
首先查看/xxx/src/main/resources/lib/xxx.so,发现正常显示ELF header,Section等信息。
接着查看target目录下的so包。
readelf -a /xxx/target/classes/lib/xxx.so,结果如下。输出提示中产生警告ELF header发生损坏。
这样结合IDE中报错大概就能够知道问题产生的环节。即项目启动,构建时生成target目录,resource目录下so文件拷贝到 target目录下时产生了这个文件损坏的问题。
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x18bdbfef
程序头起点: 4194304 (bytes into file)
Start of section headers: -4773833481741467648 (bytes into file)
标志: 0x0
本头的大小: 0 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 56
字符串表索引节头: 6
readelf:错误:Reading 3584 bytes extends past end of file for 节头
readelf:错误:Section headers are not available!
接着看一看build到底经历了哪些过程。查阅idea官网可知有4步[2]。
- 编译源码将结果输出到配置输出位置。
- 编译test中代码将结果输出到配置输出位置。
- 将resource文件拷贝到输出路径
- 报告产生的问题。
现在就确定了问题就出在步骤3上。由于我的项目是一个maven项目,resource文件拷贝到目标位置并进行相关操作会参照pom中的配置。
当时项目配置如下。查阅官网filtering含义[3]就是将directory目录下含有的占位符进行替换,然后拷贝到输出位置。这样看来一定是由于这条配置导致so包格式被破坏。
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
在官网filtering[3]介绍页面下可以看到提示。就是说二进制文件如果在resource下并且使用了true配置,那么会导致文件格式被破坏。
As already mentioned filtering binary files like images,pdf`s etc.
could result in corrupted output. To prevent such problems you can
configure file extensions which will not being filtered.
问题解决
终于找到了问题,那么也很好解决。最终根据官方提示configure file extensions[4]说明来进行配置增加过滤项。这样最终就不会对so包进行处理。增加一项配置,重新启动系统,本地及测试环境均正常,问题解决。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>so</nonFilteredFileExtension>
<nonFilteredFileExtension>dylib</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
参考资料
[1]ELF文件,http://akaedu.github.io/book/ch18s05.html
[2]build的涉及的过程,https://www.jetbrains.com/help/idea/build-process.html
[3]Maven filter配置,https://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html
[4]Maven resource过滤二进制文件,https://maven.apache.org/plugins/maven-resources-plugin/examples/binaries-filtering.html