记一次X86到arm的代码迁移实践

您所在的位置:网站首页 软件包架构不匹配怎么回事 记一次X86到arm的代码迁移实践

记一次X86到arm的代码迁移实践

2023-12-21 08:19| 来源: 网络整理| 查看: 265

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)); …… 图1 看上去像是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下的文件结构: 图2 难道说在银河麒麟的系统上需要手动把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 模板匹配失败

继续编译,报错如下: 图3 template argument deduction/substitution failed:意思是C++在进行模板推导的时候匹配失败。这个问题,又卡住了一些时间。直到和同事讨论,才找到了一些线索: 图4 std::__cxx11::basic_string是C++11定义的数据类型,会不会是数据类型不匹配从而导致模板推导失败?这篇文章给出了详细的解释,gcc5.X以后的版本默认使用std::__cxx11::basic_string作为基础字符串,之前编译代码的X86上CentOS的gcc版本为4.4.7,移植的目标机器gcc版本变成了7.3.0。根据文中的修改方法,强制使用std::__1::basic_string作为基础字符串:

CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 -O2 -std=c++0x

编译报错并没有消失,只是错误信息从std::__cxx11::basic_string 变成了std::__1::basic_string,依然是模板推导失败。来看下代码实现:

template std::string toJson(const T& val) { std::string ret; saveToJson(ret, val); return ret; }

在不改变代码功能的情况下改成:

template 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

继续编译,又出现了以下错误提示: 图5 说openssl1.1.0里边没有d2i_PKCS12_bio这个方法的定义。于是分别把OpenSSL 1.0.1e和OpenSSL 1.1.1d的源码拿出来检查,发现对应类中都是有这个方法的定义的。到这里,又一次陷入了僵局。科学上网后才在这篇文章里发现,虽然都有函数的定义及实现,不过形参结构体的原始定义还是有区别: 图6 继续查看源码确认确实如此。这样,继续使用系统自带的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

继续编译步骤,很快就报错如下: 图7 根据错误提示,需要加上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 ……

这下好了,出现了几十个编译错误: 图8 又一次考验脑细胞的时候。从内心来说,肯定希望出现的问题越少越好,而不是越来越多。于是去掉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 重新编译三方库

图9 这几十个报错中,大部分是和三方库有关: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架构的服务器上测试相关的业务逻辑。依然任重而道远。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3