前言
本文的写作目的在于装逼,没有要产生实际价值的意思。
前几天在做编译器的项目,有一个项目团队成员一直在问我ARM汇编能不能读C语言的结构体。我心想,我这生成ARM汇编的代码是用C++写的呀,又不是用汇编写的,干嘛问我这个?我跟他说,能啊。还非要我出demo。
作为一个《汇编语言程序设计》课程绩点只有5.0的渣渣,我的内心有一万只草泥马奔腾而过。好吧,菜鸡的我没说什么,默默地用二十分钟准备了相关代码、AXD Debugger仿真截图,并且标注了C语言定义的全局变量在内存中的位置、寄存器的数值。
虽然并没有什么卵用,但是能拿来装逼鸭hhhh。基于这个问题,我稍微地做了一下进一步的讨论。
先来点开胃小菜
我们先从这个简单的结构体开始,做一个实验,验证ARM能访问C语言结构体的数据。
struct
先写C语言的代码,目的是为了声明并初始化一个结构体变量,让ARM汇编进行访问。
#include
然后再来写配套的ARM汇编。
area armFunc, code, readonly
code32
export myFunc
myFunc ldr r4,[r0] ;读取t.a,其值存储在R4寄存器
ldr r5,[r0,#4] ;读取t.b,其值存储在R5寄存器
ldr r6,[r0,#8] ;读取t.c,其值存储在R6寄存器
b . ;程序卡在这里,方便我们停下来观察
end
AXD Debugger仿真结果如下:
我们对这个仿真结果做一个简单的分析。一些关键的地方我已经在图上用不同颜色的框标记出来。程序从main函数开始执行,并且为结构体t的三个成员分别进行初始化。它们的值按照小端序依次存放在内存中(红框、绿框和紫框)。
再调用myFunc函数,将结构体t的指针(0x000083FC)作为参数传递过去(根据ATPCS规范可知)。在myFunc里面利用基址变址寻址将结构体t的数据读取出来。黄框中寄存器的数值分别为三个成员的初值。
万一,类型长度不是4字节呢?
在上面给的汇编代码里,地址偏移量总是为4的倍数。这是因为int类型的数据长度正好是4字节。那么,如果用的数据类型的长度不是4字节呢?
还是举一个结构体的例子。
struct
在这个例子里,包含了char、short和int三种类型的成员。它们代表着字节数据、半字数据和字数据(字长32位)。相应地,C语言代码和对应的ARM汇编代码也要作出改动。
C语言代码如下:
struct
ARM汇编代码如下:
area armFunc, code, readonly
code32
export myFunc
myFunc ldr r4,[r0] ;读取t.a,其值存储在R4寄存器
ldrs r5,[r0,#4] ;读取t.b,其值存储在R5寄存器
ldrh r6,[r0,#6] ;读取t.c,其值存储在R6寄存器
b . ;程序卡在这里,方便我们停下来观察
end
相应的仿真结果:
程序仍然能正确读取结构体的数据。但是观察绿框可以发现:成员b占用了两个字节的空间。这是由于编译器在分配myData2各成员在内存中的位置时,以半字为单位进行字节对齐。不同的编译器在编译C语言代码时,对于字节对齐的规定有可能不同,需要具体情况具体分析。回到这个仿真结果来:因此在后续访问成员c的指令中,地址偏移量为4+2=6,而不是4+1=5。在编写结构体访问的相关指令时,要注意结构体各成员在内存空间中的分布。
算法举例:二叉树的递归先序遍历
在访问结构体数据的基础上,我们还可以用ARM汇编做链表的访问、二叉树的遍历等。这里稍微举个栗子:二叉树的先序遍历,采用递归实现。
先写一段C语言代码,主要用来准备用来先序遍历的二叉树,以及编写输出的相关代码。
#include
先序遍历的算法主体则放在了ARM汇编里。
area armFunc, code, readonly
code32
export preVisit
import myPrint
preVisit STMFD sp!,{r4,fp,lr}
;调用myPrint函数输出data成员
LDR r4,[r0]
STMFD sp!,{r0}
MOV r0,r4
BL myPrint
LDMFD sp!,{r0}
;递归调用preVisit遍历左子树
LDR r4,[r0,#4]
CMP r4,#0
BEQ right
STMFD sp!,{r0}
MOV r0,r4
BL preVisit
LDMFD sp!,{r0}
;递归调用preVisit遍历右子树
right LDR r4,[r0,#8]
CMP r4,#0
BEQ funcEnd
STMFD sp!,{r0}
MOV r0,r4
BL preVisit
LDMFD sp!,{r0}
funcEnd LDMFD sp!,{r4,fp,pc}
end
如果没有猜错的话,输出结果应该是ABCDEFG:
后记
在写作本文的过程,发现自己在不知不觉中巩固了所学知识,提高了自己的知识迁移能力。比如说用ARM汇编写二叉树的先序遍历,既巩固了自己的数据结构知识,又能将数据结构的实现迁移到ARM汇编上。
参考文献
《汇编语言程序设计——基于ARM体系结构》