首先我们了解一些基础知识。
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
CPU上的通用寄存器:
eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
介绍些常用的通用寄存器名称:
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
汇编中push/pop作用:
push 寄存器 ; 将寄存器的数据压入堆栈。
pop 寄存器 ; 出栈,恢复寄存器数据。
调用子程序前为了保存寄存器的数据不受到影响,返回时恢复原来的数据。
如果不用把寄存器里的数据临时保存一下,则可以省去上述操作。
LDR加载指令
格式为:LDR{条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。
寻址方式灵活多样,存储器地址可通过偏移量加减,移位,等运算。
STR传送指令
格式为:STR{条件} 源寄存器,<存储器地址>
STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中。
寻址方式也灵活多样,存储器地址可通过偏移量加减,移位,等运算。
前序寻址:先对基址寄存器偏移,再进行数据操作。(地址变化相当于 ++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个字节。
; Receives : ESI = 数组起始地址
; ECX = 数组长度
; Returns : EAX = 求和的结果
AERA ArraySum, CODE, READONLY ; 首先,我们定义了一个函数 ArraySum。
ENTRY
start push esi ; 然后,我们为了保存原先的esi和ecx的值,将其push进了堆栈中。
push ecx
mov eax,0 ; 将数组的和一直保存在eax寄存器中,所以用该语句将EAX寄存器初始化。
L1: 增加了一个L1的标号,用于循环,接着我们就开始了循环。
TEQ eas,#1 ;奇数判断,通过异或运算判断最后一位是否为1即可(TEQ eas,#0 ;偶数判断)
ADDEQ eax,eax,[esi] ; 对符合条件的元素进行求和。
ADD esi,esi,4 ; 然后将esi这个地址增加4(因为我们这里计算的是32位整数的和,32位整数,需要4个byte)
loop L1 ; 重复这个过程,ECX寄存器保留了数组中元素的个数。ECX寄存器保留循环的个数,并且在循环的时候每执行一次LOOP,会自减1,直至变为0循环结束。
pop ecx ; 恢复ECX和ESI寄存器原先的数据,在堆栈中弹出ecx和esi。
pop esi
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
STR R3,R0;因为R0会作为函数返回值,恢复初始的char* strDest地址
MOV PC,LR
END
调用汇编函数
extern char *strcat(char *dest, const char *src);
int main(void)
{
…….
strcat(dest, src);
…….
}