鲲鹏架构入门与实战︱鲲鹏应用迁移(附代码)

在不同的计算机架构下,相同的代码编译出的应用,有可能是不能直接运行的,本文讲解在x86和鲲鹏架构下应用需要迁移的原因(www.xtdLt.com)。

01

不同架构下程序执行对比

通过一个简单的C 程序,演示一下在不同架构下编译运行的对比,要对比的环境如表5-1所示。

1

方式1

步骤1: 准备好x86架构的运行环境,安装CentOS操作系统,并且安装好标准C开发环境,具体的步骤可以参考4.2节准备软件环境的内容,注意CPU 架构选择x86架构。

步骤2: 创建/data/code/文件夹,然后创建x86_demo.c,命令如下:

mkdir/data/code/

cd/data/code/

vimx86_demo. c

步骤3: 按i键进入编辑模式,输入代码,然后保存并退出,代码如下:

//Chapter5/x86_demo.c

intmain( void)

{

inta= 1;

intb= 2;

intc= 0;

c=a+b;

returnc;

}

步骤4: 编译x86_demo.c,生成编译后的文件x86_demo,命令如下:

gcc-g-ox86_demox86_demo.c

注意: 这里使用了gcc的-g选项,使用该选项在编译时会额外执行如下的操作:

(1) 创建符号表,符号表包含了程序中使用的变量名称的列表。

(2) 关闭所有的优化机制,以便程序执行过程中严格按照原来的C代码进行。

这样,在后续的反编译的时候,可以用汇编代码和C源代码进行对比,便于理解汇编后的代码。

步骤5: 运行x86_demo,命令如下:

./x86_demo

因为这个演示程序没有输出,所以运行x86_demo也没有回显。

2

方式2

步骤1: 准备鲲鹏架构的C开发环境,参考4.2节准备软件环境的内容。

步骤2: 创建/data/code/文件夹,然后创建kunpeng_demo.c,命令如下:

mkdir/data/code/

cd/data/code/

vimkunpeng_demo. c

步骤3: 按i键进入编辑模式,输入代码,然后保存并退出,代码如下:

//Chapter5/kunpeng_demo.c

intmain( void)

{

inta= 1;

intb= 2;

intc= 0;

c=a+b;

returnc;

}

步骤4: 编译kunpeng_demo.c,生成编译后的文件kunpeng_demo,命令如下:

aarch64-redhat-Linux-gcc-g-okunpeng_demokunpeng_demo.c

步骤5: 运行kunpeng_demo,命令如下:

./kunpeng_demo

同样没有回显。

3

方式3

步骤1: 登录鲲鹏架构服务器。

步骤2: 从x86服务器复制编译好的x86_demo到本地,命令如下:

scp root@192. 168.0. 208:/data/code/x86_demo/data/code/

需要根据服务器的实际情况修改x86服务器的用户名和IP。

步骤3: 运行x86_demo,命令如下:

./x86_demo

系统会提示无法运行该文件,如图5-1所示。

图5-1 鲲鹏架构运行x86程序

4

方式4

步骤1: 登录x86架构服务器。

步骤2: 从鲲鹏服务器复制编译好的kunpeng_demo到本地,命令如下:

scp root@192. 168.0. 133:/data/code/kunpeng_demo/data/code/

需要根据实际情况修改Kunpeng服务器的用户名和IP。

步骤3: 运行kunpeng_demo,命令如下:

./kunpeng_demo

系统会提示无法运行该文件,如图5-2所示。

图5-2 x86架构运行鲲鹏程序

根据上面的4个小实验,可以得出这样的结论,x86架构下编译的C程序无法在鲲鹏架构下直接运行; 同样,鲲鹏架构下编译的C程序也无法在x86架构下运行。为什么会这样呢? 在5.1.2节进行有针对性的分析。

02

不同架构下汇编指令分析

1

鲲鹏架构

针对5.1.1节在鲲鹏架构下编译的程序kunpeng_demo,通过反汇编工具查看它的汇编指令,详细步骤如下:

步骤1: 安装反汇编工具objdump。objdump在工具包binutils中,可以通过yum 安装该工具包,命令如下:

yum install-y binutils

步骤2: 反编译kunpeng_demo,命令及回显如下(因为反编译后的汇编代码太多,这里只保留与main方法相关的汇编代码):

[ root@ecs-kunpeng code] #objdump -S kunpeng_demo

kunpeng_demo: file format elf64-littleaarch64

此处省略几百行代码…

...

00000000004005b0 <main>:

intmain( void)

{

4005b0: d10043ff sub sp, sp, #0x10

inta= 1;

4005b4: 52800020mov w0, #0x1 //#1

4005b8: b9000fe0 str w0, [sp, #12]

intb= 2;

4005bc: 52800040mov w0, #0x2 //#2

4005c0: b9000be0 str w0, [sp, #8]

intc= 0;

4005c4: b90007ff str wzr, [sp, #4]

c=a+b;

4005c8: b9400fe1 ldr w1, [sp, #12]

4005cc: b9400be0 ldr w0, [sp, #8]

4005d0: 0b000020 addw0, w1, w0

4005d4: b90007e0 str w0, [sp, #4]

returnc;

4005d8: b94007e0 ldr w0, [sp, #4]

}

4005dc: 910043ff addsp, sp, #0x10

4005e0: d65f03c0 ret

//此处省略几百行代码…

这样,就得到了C语言代码对应的汇编代码。

下面对main函数的代码逐行分析,同时对鲲鹏架构指令和寄存器进行简单介绍。

1) sub sp, sp, #0x10

sp寄存器保持栈顶位置,这里向下扩展了16字节,这16字节可以用来给后面的变量分配内存空间,栈默认最小扩展空间是16字节,每次扩展空间是16字节的整数倍。

2) mov w0, #0x1

把操作数1赋值给寄存器w0。

3) str w0, [sp,#12]

把w0寄存器的值传送到栈顶开始的第12字节对应的内存中。也就是给变量a赋值。

4) mov w0, #0x2

把操作数2赋值给寄存器w0。

5) str w0, [sp,#8]

把w0寄存器的值传送到栈顶开始的第8字节对应的内存中,也就是给变量b赋值。

6) str wzr, [sp,#4]

把零寄存器的值传送到栈顶开始的第4字节对应的内存中,也就是给变量c赋值。零寄存器的值总是0。

7) ldr w1, [sp,#12]

把栈顶开始的第12字节对应的内存数据传送给w1寄存器,也就是把变量a读到寄存器w1。

8) ldr w0, [sp,#8]

同上,把变量b读到寄存器w0。

9) add w0, w1, w0

把w0和w1相加,存到w0寄存器中。

10) str w0, [sp,#4]

把寄存器w0的值写到变量c中。

11) ldr w0, [sp,#4]

把变量c中的值写回寄存器w0,w0用来作为返回值寄存器。

12) add sp, sp, #0x10

恢复栈空间,释放内存。

注意: 这里使用了objdump的-S选项,该选项将代码段反汇编的同时,将反汇编代码和源代码交替显示。该选项需要gcc在编译时使用-g的选项。

2

x86架构

针对5.1.1节在x86架构下编译的程序x86_demo,通过反汇编工具查看它的汇编指令,详细步骤如下:

步骤1: 安装反汇编工具objdump。objdump在工具包binutils中,可以通过yum 安装该工具包,命令如下:

yum install-y binutils

步骤2: 反编译x86_demo,命令及回显如下(只保留与main方法相关的汇编代码):

[root@ecs-x86 code]#objdump -S x86_demo

x86_demo: file format elf64-x86 -64

此处省略几百行代码…

00000000004004ed <main>:

intmain(void)

{

4004ed: 55push %rbp

4004ee: 4889e5 mov %rsp,%rbp

inta= 1;

4004f1: c7 45fc 01000000movl $ 0x1, -0x4(%rbp)

intb= 2;

4004f8: c7 45f8 02000000movl $ 0x2, -0x8(%rbp)

intc= 0;

4004ff: c7 45f4 00000000movl $ 0x0, -0xc(%rbp)

c=a+b;

400506: 8b 45f8 mov -0x8(%rbp),%eax

400509: 8b 55fc mov -0x4(%rbp),%edx

40050c: 01d0 add %edx,%eax

40050e: 8945f4 mov %eax, -0xc(%rbp)

returnc;

400511: 8b 45f4 mov -0xc(%rbp),%eax

}

400514: 5dpop %rbp

400515: c3 retq

400516: 662e 0f1f840000nopw %cs: 0x0(%rax,%rax, 1)

40051d: 000000

//此处省略几百行代码…

对main函数的每行代码简要解释如下:

1) push %rbp

将调用函数的栈帧栈底地址入栈,即将bp寄存器的值压入调用栈中。

2) mov %rsp,%rbp

建立新的栈帧,将main函数的栈帧栈底地址放入bp寄存器中。sp和bp是两个指针寄存器,一般的函数调用都会使用上述两个指令。

把1传送给变量a(整型变量占用4字节,所以这里是-0x4)。

把2传送给变量b。

把0传送给变量c。

6) mov -0x8(%rbp),%eax

把变量b的值传送给eax寄存器。

7) mov -0x4(%rbp),%edx

把变量a的值传送给edx寄存器。

8) add %edx,%eax

edx和eax寄存器相加并存入eax寄存器。

9) mov %eax,-0xc(%rbp)

把eax寄存器的值传送给变量c。

10) mov -0xc(%rbp),%eax

把变量c的值传送给寄存器eax,eax作为返回值寄存器。

11) pop %rbp

恢复上一栈帧的bp。

03

应用需要迁移的原因

1

汇编代码角度

从5.1.2节的汇编代码分析可以看出来,同样的C语言代码,在编译成不同架构下的程序后,得到的汇编代码是不同的,这些不同点主要体现在以下3个方面:

1) 处理器指令

以简单的给变量赋值操作为例:

(1) 鲲鹏架构。

首先使用mov指令把操作数传送给寄存器,然后使用ldr指令把寄存器的值传送到内存。

(2) x86架构。

直接使用movl指令把操作数传送到内存。

鲲鹏架构和x86架构在具体的处理器指令设计上,是有重大区别的,同样的功能,两个架构的处理器指令实现的方式可能不一样。

2) 寄存器

这一点也比较明显,两个架构下的寄存器不管是数量还是功能都有所不同。

(1) 鲲鹏架构。

ARM 64有34个寄存器,其中编号x0~x29是通用寄存器,x30为程序链接寄存器,x31比较特殊,它有时候用作xzr零寄存器,有时候是栈指针寄存器sp,两者不能在一条指令里共存,另外两个寄存器是程序计数器PC、状态寄存器CPSR。

ARM 64寄存器x0~x30及xzr零寄存器都是64位的,它们的低32位构成了32位寄存器,分别用w0~w30表示,用wzr表示32位下的零寄存器。

除此之外,ARM 64还有浮点寄存器和向量寄存器,此处就不详细介绍了。

(2) x86-64架构。

x86-64架构下有16个64位的通用寄存器,这些寄存器支持访问低位,例如访问低8位、低16位、低32位。

16个寄存器的名称和用途如表5-2所示。

3) 指令长度

x86下指令的长度是不一样的,短的只有1字节,而长的却有15字节,给寻址带来了一定的不便。

鲲鹏架构指令长度为固定的32位(ARM 工作状态),寻址方便,效率较高。

2

计算技术栈角度

对于常用的使用高级语言编写的应用,计算技术栈一般分为两类,一类是编译型语言,另一类是解释型语言,这两种技术栈的示意图如图5-3所示。

1) 编译型语言

编译型语言的代表是C/C++ 等语言,使用编译型语言编写的源代码经过一系列的编译过程,最终才能生成可执行程序,大体流程如图5-4所示。

图5-3 技术栈示意图

图5-4 C编译过程

因为不同架构下指令集不同,导致依赖于指令集的二进制机器码、汇编语言都不同,所以同一段程序,在不同的架构下,需要最终编译成和架构相适应的二进制机器码。这也就解释了5.1节最后两个实验不能成功的原因,毕竟架构不同,在特定架构下编译的二进制机器码,它需要执行的指令在另一个架构下根本就不存在。

2) 解释型语言

解释型语言的代表是Java/Python等语言,从图5-3可以看出,Java的源代码会被编译成字节码,字节码运行在Java虚拟机JVM 上,JVM 具有与平台架构无关的指令集,同一段Java代码在不同的架构下都可以编译成相同的字节码。JVM 对字节码进行解释,转换为物理CPU 对应的机器码进行实际执行。因为不同的指令集架构可以适配不同的JVM 实现,所以Java等解释型语言只需编译一次,就可以到处运行,不同架构下的JVM 屏蔽了指令集之间的差异。

如果一个应用是使用纯Java语言编写的,理论上基本可以跨平台运行,但是实际情况比较复杂,有些Java应用会引用so库文件,这些so库文件很有可能是通过编译型语言例如C来编写的,这时候就要考虑so库文件的移植。

03

源代码下载

关注微信公众号,后台回复关键词 “鲲鹏应用迁移” 即可获得完整源代码。

04

参考图书

鲲鹏架构入门与实战

ISBN:978-7-302-57687-7

张磊 编著

定价:129元

05

精彩推荐

  • Python边做边学︱初始股票(附代码)

  • Python边做边学︱猜单词游戏(附代码)

  • Python边做边学︱疯狂僵尸游戏(附代码)

  • Python边做边学︱小猪佩奇游戏(附代码)

  • Python 韩信点兵思政案例(含优惠码)

  • Python ︱爬取天气预报信息(附视频)

  • 《机器学习》实验指导书(附实验参考+代码)

  • Python爬虫综合实战 │ 创建云起书院爬虫(附代码)

  • Python爬虫实战 │ Email提醒(附代码)

  • Python深度学习 │一文掌握卷积神经网络

  • Python爬虫实战 │ 用selenium爬取百度表情包(附代码)

  • P ython爬虫实战│状态521网页的爬取

  • Python爬虫实战│爬取天气数据的实例详解(附源码)

  • Python实训:用贪婪算法分析业务员路径问题|附源码

主营产品:封口机,热收缩机,真空机,打包装机