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 -lboost_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文件即可。给出命令:
./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.0.tar.gz
安装好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 -ljson_linux-gcc-4.4.6_libmt -lssl -lboost_system -lpthread
……
这下好了,出现了几十个编译错误:
又一次考验脑细胞的时候。从内心来说,肯定希望出现的问题越少越好,而不是越来越多。于是去掉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的依赖,从那几十个报错信息中查找线索。
4.3.2 重新编译三方库
这几十个报错中,大部分是和三方库有关:boost、libjson。可是编译这两个库的时候没出什么错。先尝试编译报错的其中一个库:
./b2 install --libdir=/usr/local/mdm/boost/lib/release variant=release link=static threading=multi runtime-link=static --with-log
将库文件拷贝出来放到对应路径下后,和libboost_log_setup.a 库有关的报错没有了!看来真是三方库的编译有点问题。这样将boost的库一个个单独编译后,boost库的错误提示没有了。重新编译libjson,放到对应路径下编译通过。可能是最开始编译三方库的时候有什么操作步骤错了,编译的命令还是一样的。
4.4 第四个模块test的编译
从代码实现来看,这个模块主要是一些单元测试代码。依然改了小部分代码,这里就不贴出来了。最后,执行生成rpm包的Makefile,成功生成了rpm包。
5、结语
到这,编译算是通过了。接下来的任务就是要在arm架构的服务器上测试相关的业务逻辑。依然任重而道远。