首先我们了解一些基础知识。
char类型的大小:在32位RAM处理器的C语言中,char类型变量占一个字节。
int类型的大小:在32位RAM处理器的C语言中,int代表4个字节(32位)。
异或:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
掩码:掩码是一串二进制代码对目标字段进行位与运算。
TEQ — 测试位: TST{条件} {P} <op1>, <op2>
TEQ不会修改操作数。对2个数,进行EOR。
符号:
汇编分号;
的作用:分号后是注释,类似于c语言的//
汇编中括号[]
的作用:一般说来,加中括号 [ ] 表示一种间接的取操作数方式,有点类似于C语言中的指针解引用的概念.,类似于c语言的*p
汇编语言中判断奇偶数怎么判断:
二进制的第1位为0,则是偶数;为1相反。
所以偶数的特点是换算成二进制的话最后一位必定是0(2的倍数),所以检测最后一位是否是0就能判断出是否是偶数,检测最后一位是否是1就能判断出是否是奇数。
标号: 在汇编语言中用来表示地址的符号就叫做标号。
子程序的调用与返回:
为进行识别,子程序的第1条指令之前必须赋予一个标号,以便其他程序可以用这个标号调用子程序。
在调用子程序的同时,也可以使用R0~R3 来进行参数的传递和从子程序返回运算结果。
在 ARM 汇编语言程序中,主程序一般通过 BL
指令来调用子程序。该指令在执行时完成如下操作:将子程序的返回地址存放在连接寄存器LR中,同时将程序计数器PC指向子程序的入口点。
子程序结尾一般通过指令MOV PC,LR
返回主程序。
外部可引用符号声明伪指令EXPORT
用伪指令EXPORT可以声明一个其他源文件可引用的符号,这种符号也叫做外部可引用符号。
EXPORT 符号
引用外部符号声明伪指令IMPORT
当在一个源文件中需要使用另外一个源文件的外部可引用符号时,在被引用的符号前面必须使用伪指令 IMPORT 对其进行声明。
IMPORT 符号
段定义伪指令
格式:AREA <段名> {<属性>}{,<属性>}…
ENTRY伪指令用于指定汇编程序的入口点。
END伪指令用于通知编译器汇编工作到此结束,不再往下汇编了。
AREA 段名, CODE, 属性
ENTRY;
......(内容)
END
LDR加载指令
格式为:LDR{条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。
寻址方式灵活多样,存储器地址可通过偏移量加减,移位,等运算。
STR传送指令
格式为:STR{条件} 源寄存器,<存储器地址>
STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中。
寻址方式也灵活多样,存储器地址可通过偏移量加减,移位,等运算。
MOV指令和LDR/STR指令的区别:
ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。
比如想把数据从内存中某处读取到寄存器中,只能使用ldr;把寄存器中的数据存放到内存的某处地址中,只能使用str。
而mov不能干这个活,mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中。
前序寻址:先对基址寄存器偏移,再进行数据操作。(地址变化相当于 ++i)
格式:LDR/STR 寄存器1,[寄存器2,#立即数(偏移量)]
后序寻址:先进行数据操作,再对基址寄存器偏移。(地址变化相当于i++)
格式:LDR/STR 寄存器1,[寄存器2],#立即数(偏移量)
变量数据类型 对应的LDR/STR指令
汇编主函数格式:
AREA 段名, CODE, READONLY ;只读的代码段
段名
ENTRY ;程序入口点
start
…….
代码段
…….
END ;段结束
汇编子函数格式:
AERA 段名, CODE, READONLY
ENTRY
段名
......
代码段
......
MOV PC,LR
END
下面是博主对这两题的理解和答案,不保证100%的正确率。
好的,现在我们开始分析例题。
- 例题4
; 整数数组求和 一个数占4个字节。
; R1 = 数组起始地址
; R2= 数组长度
; Returns : R0 = 求和的结果
AERA ArraySum, CODE, READONLY ; 首先,我们定义了一个函数 ArraySum。
ENTRY
start
MOV R0,#0 ; 将数组的和一直保存在R0寄存器中,所以用该语句将寄存器初始化。
L1 ; 增加了一个L1的标号,用于循环,接着我们就开始了循环。
TEQ [R1],#1 ;奇数判断,通过异或运算判断最后一位是否为1即可(TEQ eas,#0 ;偶数判断)
ADDEQ R0,R0,[R1] ; 对符合条件的元素进行求和。
ADD R1,R1,4 ; 然后将R1这个地址增加4(因为我们这里计算的是32位整数的和,32位整数,需要4个byte)
SUBS R2,R2,#1
BNE L1 ; 重复这个过程,R2寄存器保留了数组中元素的个数,并且在循环的时候每执行一次减1,直至变为0循环结束。
END
- 例题5
c函数原型:char *strcat(char *dest, const char *src);
汇编实现strcat函数(需要参考strcat函数原型)
Strcat函数原型:
char* strcat(char* strDest , const char* strSrc)
{
char* address=strDest;
assert( (strDest!=NULL)&&(strSrc!=NULL) );//对源地址和目的地址加非0断言,即如果字符串为空,程序会终止运行。
while(*address)//是while(*strDest!=’\0’)的简化形式
{
//若使用while(*strDest++),则会出错,因为循环结束后strDest还会执行一次++,
//那么strDest将指向'\0'的下一个位置。/所以要在循环体内++;因为要使*strDest最后指
//向该字符串的结束标志’\0’。
address++;
}
while(*address++=*strSrc++);
//此处可以加语句*strDest=’\0’;无必要,因为是字符串,系统会自动补\0。
return strDest;//将目的地址返回
}
ATPCS关于堆栈和寄存器的使用规则
ATPCS 标准规定,对于参数个数不多于 4 的函数,编译器必须按参数在列表中的顺序,自左向右 为它们分配寄存器 R0~R3。其中函数返回时,R0 还被用来存放函数的返回值。
汇编实现strcat函数
char* strcat(char* strDest , const char* strSrc)
r0对应形参char* strDest,r1对应形参 const char* strSrc
r0还是返回值char*
AREA strcat,CODE,READONLY
EXPORT strcat
strcat
LDR R3,R0;备份初始的char* strDest地址
L1 ; 相当于原型strcat函数中的第一个循环体,让R2寄存器指向第一个字符串的‘\0’。
LDRB R2,[R0],#1 ;后续寻址
CMP R2,#0 ;相当于取的内容和'\0'进行比较
BNE L1
L2 ; 相当于原型strcat函数中的第二个循环体。第二个字符串接在第一个字符串后面,直到遇到第二个字符串的'\0'结束循环。
LDRB R2,[R1],#1
STRB R2,[R0],#1
CMP R2,#0
BNE L2
MOV R0,R3;因为R0会作为函数返回值,恢复初始的char* strDest地址
MOV PC,LR
END
调用汇编函数
extern char *strcat(char *dest, const char *src);
int main(void)
{
…….
strcat(dest, src);
…….
}