linux内核接口——Linux二进制兼容性问题
2016-12-21 15:17
267 查看
Linux上二进制有一个显著的特点就是可移植性不强。我们在不同的发行版之间,不同的内核版本之间,程序往往是不能通用的。
硬件平台
众所周知的,硬件平台不一样,指令集不一样,二进制就几乎没有可移植的能力。
而ABI的不同则是linux内核和glibc的升级导致的规范变化导致的。不同的ABI程序和库在不同的环境下很高概率是不能运行的,除非是低版本最原始的ABI在现代系统上跑一般都可以向下兼容。而这种不兼容主要发生在C++身上,因为C++近几年的特性改变速度相对较快,管理困难。X86上常见的elf ABI有:
OS/ABI: UNIX - Linux
OS/ABI: UNIX - System V
OS/ABI: UNIX - GNU
其中GNU和Linux两种是相同的,只是使用不同版本的readelf会现实不同的结果。而system v则是最古老的,也是兼容性最好的。有的老一些的系统上只识别system v的ABI。但是system v ABI for x86_64却是比linux还要先进的ABI。因为这个ABI把大部分的参数转由寄存器传递,而不是由栈传递,对栈的使用减少就增加了以往的缓存溢出的难度。还有x32上的return to libc等攻击的手法也得变通,难度提高。
这里面就是规定的最低支持的内核版本,运行时会直接检查这个与当前内核版本之间的区别,不满足就会FATAL: kernel too old。这种不兼容你也可以通过file命令发现,这个命令会输出二进制的最小兼容的版本。有的库也携带着这个限制,有的则没有,你的进程所依赖的任何一个库满足了这个限制都会导致执行不成功。
而各个系统所有的库的版本不一样,很多库的调用名的symbol都会在后面追加版本号,如果版本号不匹配,库则不能通用,例如我们经常见到libc.so.6: version `GLIBC_2.14' not found这类的打印。strings /lib64/libc.so.6 |grep GLIBC_ 通过这个命令可以排查,大部分的库问题根源都在libc,但是不是绝对的。libgcc_s.so.1是GCC的组件,编译时候运行时候都需要,一个版本GCC编译的程序常常不能在装有另一个版本GCC的平台上运行,就是这个原因,所以从高到低版本的迁移需要带着它。libc.so.6是最底层的库,操作系统和其中所有应用程序几乎都依赖,是应用程序能够跟操作系统通信的基础。原本UNIX中的libc和GNU开发的第三方版本glibc,像这里的名字虽然是libc,但事实上就是glibc,功能没有太大差别。libm.so.6则是对libc里面的数学部分优化后的版本。
另外一个需要注意的地方是连接的时候不需要指定后面的版本号,因为系统一般会建立到这个版本号的软链接,现代的gcc即使在连接的时候不指定版本号,并且不存在软连接,他也能正确的找到完整名字的库文件,实际的链接的是带版本号的库,所以即使你在编译的时候使用了不带库版本号的动态库,使用时还是需要携带带版本号的库文件。
硬件平台
众所周知的,硬件平台不一样,指令集不一样,二进制就几乎没有可移植的能力。
ABI
在架构上,必须要区分x86和x64两种架构,一般的x64的机器都能运行x86的程序,但是如果你把程序编译为x86,你就得面对大量的x64服务器的性能瓶颈。这个架构上的区别在任何的平台上都一样的,并不是linux的特有问题。而ABI的不同则是linux内核和glibc的升级导致的规范变化导致的。不同的ABI程序和库在不同的环境下很高概率是不能运行的,除非是低版本最原始的ABI在现代系统上跑一般都可以向下兼容。而这种不兼容主要发生在C++身上,因为C++近几年的特性改变速度相对较快,管理困难。X86上常见的elf ABI有:
OS/ABI: UNIX - Linux
OS/ABI: UNIX - System V
OS/ABI: UNIX - GNU
其中GNU和Linux两种是相同的,只是使用不同版本的readelf会现实不同的结果。而system v则是最古老的,也是兼容性最好的。有的老一些的系统上只识别system v的ABI。但是system v ABI for x86_64却是比linux还要先进的ABI。因为这个ABI把大部分的参数转由寄存器传递,而不是由栈传递,对栈的使用减少就增加了以往的缓存溢出的难度。还有x32上的return to libc等攻击的手法也得变通,难度提高。
大小端
一个elf支持被编译成各种大小端,我们不能用编译成大端的elf在小端的内核上执行。但是考虑到intel的CPU全部是小端,所以我们在intel平台上编译部署不需要过多的考虑这个问题内核版本
Gcc在编译的时候可以使用--enable-kernel指定最低支持的内核版本,这个选项会在elf头部添加.note.ABI-tag,如果你用readelf读取头部,会发现:这里面就是规定的最低支持的内核版本,运行时会直接检查这个与当前内核版本之间的区别,不满足就会FATAL: kernel too old。这种不兼容你也可以通过file命令发现,这个命令会输出二进制的最小兼容的版本。有的库也携带着这个限制,有的则没有,你的进程所依赖的任何一个库满足了这个限制都会导致执行不成功。
库
Gcc倾向于动态编译和动态加载,golang就是吐槽这一点的生动代表。但是动态编译携带库确实能减小大工程结果文件大小,而且这是linux系统的传统价值观。所以在很多地方还是有必要性的。而各个系统所有的库的版本不一样,很多库的调用名的symbol都会在后面追加版本号,如果版本号不匹配,库则不能通用,例如我们经常见到libc.so.6: version `GLIBC_2.14' not found这类的打印。strings /lib64/libc.so.6 |grep GLIBC_ 通过这个命令可以排查,大部分的库问题根源都在libc,但是不是绝对的。libgcc_s.so.1是GCC的组件,编译时候运行时候都需要,一个版本GCC编译的程序常常不能在装有另一个版本GCC的平台上运行,就是这个原因,所以从高到低版本的迁移需要带着它。libc.so.6是最底层的库,操作系统和其中所有应用程序几乎都依赖,是应用程序能够跟操作系统通信的基础。原本UNIX中的libc和GNU开发的第三方版本glibc,像这里的名字虽然是libc,但事实上就是glibc,功能没有太大差别。libm.so.6则是对libc里面的数学部分优化后的版本。
另外一个需要注意的地方是连接的时候不需要指定后面的版本号,因为系统一般会建立到这个版本号的软链接,现代的gcc即使在连接的时候不指定版本号,并且不存在软连接,他也能正确的找到完整名字的库文件,实际的链接的是带版本号的库,所以即使你在编译的时候使用了不带库版本号的动态库,使用时还是需要携带带版本号的库文件。
编译器
Gcc的5.1版本的编译器会在编译时做大量激进的优化,但是有的优化是只对于最新的CPU特性有效,老一些的CPU在硬件层面就不支持这些优化,所以如此编译的程序就有兼容性问题。方法是用更老的编译器或者是用5.2之后解决了这个问题的更新的编译器。相关文章推荐
- VC++中接口的二进制兼容性
- Linux on POWER:发行版迁移和二进制兼容性考虑事项
- javaweb文件上传路径在windows和linux上的兼容性问题
- Linux下配置Oracle调用接口OCI及我碰到的一些问题
- Linux下的二进制兼容性的检测
- Linux网卡接口没有eth0,却有eth1的问题
- 64位arm_Linux操作系统驱动兼容性问题
- Linux 环境下思源黑体字体与 Java 之间的兼容性问题的解决(补充说明)
- linux涉及的常见网络接口设置和特殊路由问题汇总
- RedFlag桌面6.0 Linux的安装 与Nvidia GeForce Mx440兼容性问题
- Linux 系统的兼容性问题
- 关于linux内核 linux-2.6.34 编译报 unable to find suitable fs in /proc/mount,is it mounted? 问题说明
- windows下用caffe加载二进制模型(linux下训练)的问题
- Linux下Wireshark普通用户不能获取网络接口问题
- 关于接口兼容性的问题
- 总结一下编译linux内核 linux-2.6.34所遇到的问题。
- VC++中接口的二进制兼容性
- 浅谈Linux 二进制包安装MySQL的一些问题
- JZ2440ARM在Linux3.4.2中移植GPIO的Sysfs接口时没有相关配置项的问题
- Linux 环境下思源黑体字体与 Java 之间的兼容性问题的解决