记⼀次X86到arm的代码迁移实践
1、背景
⽬前政企的软件,好多都要求进⾏国产化适配。项⽬上的代码也需要做国产化适配,主要是从X86_64+CentOS6.7系统移植到arm(鲲鹏)+银河麒麟V10系统,需在⽬标系统上编译出rpm包。这次移植,踩了很多坑,也缺乏代码移植相关的经验,希望能对正在做移植的开发⼈员有所帮助。
2、开始之前
可以先搭建鲲鹏官⽅的, 先⼤致分析⼀下代码是否有需要改动的地⽅以及改动的⼯作量。分析结果可适当参考,我的代码提⽰没有修改点,后⾯其实还是改了⼀些。
3、三⽅库编译
开始移植的第⼀步,是先确认有哪些第三⽅库,所需的三⽅库都要先在新的系统上编译⼀遍。项⽬采⽤Makefile编译,打开Makefile⽂件查看:
THIRD_DIR := $(ROOT_DIR)/3rd_party
THIRD_LIB := boost jsoncpp
THIRD_INC := $(foreach lib, $(THIRD_LIB),-I$(THIRD_DIR)/$(lib)/include)
THIRD_LINK := $(foreach lib, $(THIRD_LIB),-L$(THIRD_DIR)/$(lib)/lib/linux/release)
……
LDFLAGS +=……-ljson_linux-gcc-4.4.6_libmt -lssl -lboost_log_setup -lboost_log -lcurl -lboost_regex -lboost_thread -lboost_filesystem -lboost_system -lbo ost_program_options -lpthread
可以看到,项⽬⽤到的三⽅库包括boost和jsconcpp。项⽬开始的时候是C++11刚发布的那⼏年,先在boost库已经纳⼊C++标准库了;jsoncpp⽤于解析json数据。
3.1boost库的编译
3.1.1boost库版本的确定
boost库版本位于源代码⽬录下的version.hpp下,
#define BOOST_VERSION 105400
……
#define BOOST_LIB_VERSION "1_54"
很显然,项⽬的boost库版本为1.54。需要下载boost1.54的全包,利⽤⾥⾯的脚本⽂件⽣成b2,再⽤b2⽣成相应的库。boost每个版本的编译略有区别,请注意查看全包⾥⾯的index.htm.
3.1.2⽣成相应的库⽂件
项⽬使⽤的静态库⽂件,只需⽣成.a⽂件即可。给出命令:
x86架构和arm架构区别
./bootstrap.sh --prefix=/usr/local/mdm/boost
./b2 install --libdir=/usr/local/mdm/boost/lib/release variant=release link=static threading=multi runtime-link=static --with-filesystem --with-program_options --with-regex --with-system --with-thread --with-date_time --with-test --with-log
第⼀⾏代码⽣成b2⽂件,并制定输⼊路径;
第⼆⾏代码制定库存放路径、编译静态库以及所需的库。如果直接运⾏./b2也是可以的,会在代码路径下⽣成所有库动态库及静态库
(.so、.a⽂件)。
编译完成后,将.a⽂件拷贝到项⽬的依赖包存放路径下。
3.2jsoncpp库的编译
项⽬的jsoncpp版本为0.6.0,这个版本的jsoncpp编译需要依赖scons,scons解压可⽤:
tar -zxvf scons-2.2.
安装好scons后,解压jsoncpp源码后,进⼊源码所在路径执⾏以下命令:
python ../scons-2.2.0/script/scons platform=linux-gcc
../scons-2.2.0/script/scons是scons安装路径,编译完成后libs/linux-gcc-7.3.0在即可得到相应库:libjson_linux-gcc-7.3.0_libmt.a、libjson_linux-gcc-7.3.0_libmt.so。
4、开始编译
准备⼯作完成,可以开始编译。项⽬分为四个主要模块,通过Makefile⽂件,可以出最先编译的模块进⾏编译。
……
APPS = $(SRC_DIR)/libutil $(SRC_DIR)/libcs $(SRC_DIR)/cs $(SRC_DIR)/test
……
可以看到,项⽬会依次编译libutil、libcs、cs、test四个模块。
4.1第⼀个模块libutil的编译
4.1.1openssl依赖
开始执⾏执⾏Makefile之后,很快得到了报错如下:
…/3rd party/boost/include/boost/asi0/ss1/detail/impl/openssl_init.ipp:43:23: error: expected id-expression before '( token43 mutexes_resize(::CRYPTo_num_locks));
……
看上去像是openssl没有安装好。⼀般来说,Linux系统都会预装openssl,作为系统应⽤。难道是麒麟V10没有安装openssl?使⽤ssh -V分别查看CentOS和麒麟V10openssl版本:
CentOS:
OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013
麒麟V10:
OpenSSH_7.8p1, OpenSSL 1.1.1d  10 Sep 2019
麒麟V10确实安装了openssl,且版本更⾼。那为什么会编译不过呢?初次进⾏代码移植且对Linux系统只有浅薄理解;且受鲲鹏代码迁移⼯具分析结果的影响,认为不⽤修改代码的笔者在这个问题上折腾不少时间。编译都是依赖Makefile,那还是得从Makefile⽂件看起。
……
THIRD_DIR := $(ROOT_DIR)/3rd_party
THIRD_LIB := boost jsoncpp
THIRD_INC := $(foreach lib, $(THIRD_LIB),-I$(THIRD_DIR)/$(lib)/include)
THIRD_LINK := $(foreach lib, $(THIRD_LIB),-L$(THIRD_DIR)/$(lib)/lib/linux/release)
CFLAGS +=-O2 -std=c++0x
CXXFLAGS +=-O2 -std=c++0x
CPPFLAGS +=-O2 -std=c++0x -I$(SRC_DIR)/libutil -I$(SRC_DIR)/libcs $(THIRD_INC)
LDFLAGS +=-lrt -L$(SRC_DIR)/libutil -L$(SRC_DIR)/libcs $(THIRD_LINK)-lcs -lutil -ljson_linux-gcc-4.4.6_libmt -lssl -lboost_system -lpthread
……
可以看到,原先的Makefile脚本在编译的过程中通过-I-L两个参数定义了编译过程中头⽂件及库⽂件的寻路径,上述Makefile⾥⾯
的LDFLAGS同时指定了库⽂件需要寻的库⽂件,如-lcs表⽰在库⽂件寻路径下寻libcs.a这个静态库。
以boost为例,-I表⽰的头⽂件搜寻路径为:
$(ROOT_DIR)/3rd_party/boost/include-->/usr/include-->/usr/local/include
同理,-L表⽰的库⽂件搜寻路径为:
$(ROOT_DIR)/3rd_party/boost/lib--/lib-->/usr/lib-->/usr/local/lib
详尽介绍可参考。
查看下openssl下的⽂件结构:
难道说在银河麒麟的系统上需要⼿动把include⽂件夹拷贝到/usr/include⽬录下吗?试过之后还是报相同的错。直到有天晚上回家突然想起,会不会include_linux才是Linux的头⽂件⽬录⽽include是windows的⽬录?隔天将include_linux下的⽂件重命名为openssl放
到/usr/include⽬录下,果然问题不在。其实这个地⽅的改法并不准确,后⾯会提。
4.1.2 curl依赖
第⼀个问题解决后,继续执⾏make命令,很快出现了新的问题,问题⽐较好处理。根据报错提⽰信息,是由于没有安装curl依赖造成,执⾏以下命令:
yum install libcurl-devel libcurl-dev
安装curl依赖即可。
4.1.3 模板匹配失败
继续编译,报错如下:
template argument deduction/substitution failed:意思是C++在进⾏模板推导的时候匹配失败。这个问题,⼜卡住了⼀些时间。直到和同事讨论,才到了⼀些线索:
std::__cxx11::basic_string是C++11定义的数据类型,会不会是数据类型不匹配从⽽导致模板推导失败?给出了详细的解释,gcc5.X以后的版本默认使⽤std::__cxx11::basic_string<char>作为基础字符串,之前编译代码的X86上CentOS的gcc版本为4.4.7,移植的⽬标机器gcc版本变成了7.3.0。根据⽂中的修改⽅法,强制使⽤std::__1::basic_string<char>作为基础字符串:
CXXFLAGS +=-D_GLIBCXX_USE_CXX11_ABI=0 -O2 -std=c++0x
编译报错并没有消失,只是错误信息从std::__cxx11::basic_string<char> 变成了std::__1::basic_string<char>,依然是模板推导失败。来看下代码实现:
template<typename T>
std::string toJson(const T& val){
std::string ret;
saveToJson(ret, val);
return ret;
}
在不改变代码功能的情况下改成:
template<typename T>
std::string toJson(const T& val){
Json::Value ret;
saveToJson(ret, val);
Json::StyledWriter writer;
return writer.write(ret);//将Json::Value转成string
}
再次编译,这次终于编译通过了。⾄于为什么在⾼版本的gcc编译失败,推测是⾼版本的gcc对模板的类型检查要求更加严格导致。
4.1.4 openssl
继续编译,⼜出现了以下错误提⽰:
说openssl1.1.0⾥边没有d2i_PKCS12_bio这个⽅法的定义。于是分别把OpenSSL 1.0.1e和OpenSSL 1.1.1d的源码拿出来检查,发现对应类中都是有这个⽅法的定义的。到这⾥,⼜⼀次陷⼊了僵局。科学上⽹后才在⾥发现,虽然都有函数的定义及实现,不过形参结构体的原始定义还是有区别:
继续查看源码确认确实如此。这样,继续使⽤系统⾃带的openssl必然会编译不过;从⽅便移植的⾓度,也应该把⽼的openssl放到三⽅库⽬录下⼀起编译以此⼀劳永逸地解决openssl的问题。
……
THIRD_DIR := $(ROOT_DIR)/3rd_party
THIRD_LIB := boost jsoncpp openssl
THIRD_INC := $(foreach lib, $(THIRD_LIB),-I$(THIRD_DIR)/$(lib)/include)
THIRD_LINK := $(foreach lib, $(THIRD_LIB),-L$(THIRD_DIR)/$(lib)/lib/linux/release)
CFLAGS +=-O2 -std=c++0x
CXXFLAGS +=-O2 -std=c++0x
CPPFLAGS +=-O2 -std=c++0x -I$(SRC_DIR)/libutil -I$(SRC_DIR)/libcs $(THIRD_INC)
LDFLAGS +=-lrt -L$(SRC_DIR)/libutil -L$(SRC_DIR)/libcs $(THIRD_LINK)-lcs -lssl -lcrypto -lutil -ljson_linux-gcc-4.4.6_libmt -lssl -lboost_system -lpthread ……
Makefile⽂件⾥加上openssl的依赖,再次编译成功⽣成了.O⽂件。到这,第⼀个模块libutil的编译算是完成了。
4.2 第⼆个模块libcs的编译
在成功编译了libutil后,依赖关系已经处理好了,第⼆个模块没遇到什么问题便成功了。继续下个模块的编译。
4.3第三个模块cs的编译
4.3.1 libdl
继续编译步骤,很快就报错如下:
根据错误提⽰,需要加上glibc的依赖
……
LDFLAGS +=-ldl -lrt -L$(SRC_DIR)/libutil -L$(SRC_DIR)/libcs $(THIRD_LINK)-lcs -lssl -lcrypto -lutil -l
json_linux-gcc-4.4.6_libmt -lssl -lboost_system -lpthre ad
……
这下好了,出现了⼏⼗个编译错误:
⼜⼀次考验脑细胞的时候。从内⼼来说,肯定希望出现的问题越少越好,⽽不是越来越多。于是去掉glibc依赖,根据错误提⽰来解决办法:
……
LDFLAGS +=-lrt -L$(SRC_DIR)/libutil -L$(SRC_DIR)/libcs $(THIRD_LINK)-lcs -lssl -lcrypto -lutil -ljson_linux-gcc-4.4.6_libmt -lssl -lboost_system -lpthread ……
根据错误提⽰:
undefined reference to symbol 'dlsym@@GLIBC_2.17
参考难道是glibc2.17⾥边没有定义dlsym?执⾏strings /lib64/libc.so.6 | grep GLIBC来查看两台服务器
到底装了哪些版本的glibc。CentOS:
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_PRIVATE
麒麟V10
GLIBC_2.17
GLIBC_2.18
GLIBC_2.22
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_2.27
GLIBC_2.28
GLIBC_PRIVATE
可以看到,麒麟系统上最低版本的glibc也⽐CentOS的最⾼版本⾼了很多。难道真的是glibc⾼版本没有dlsym?试着下载glibc各版本的代码来检查,这个⽅法⼯作量颇⼤。仔细分析,如果是⼀个系统函数,不应该莫名其妙地没了或者变了,⽽更应该考虑⾃⼰的代码是否有问题。于是继续加上glibc的依赖,从那⼏⼗个报错信息中查线索。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。