我们编写的汇编程序还是不够底层,CPU都是对机器码进行操作的,所以还需要用汇编器将汇编代码转换成机器码才能被CPU处理。下面举几个例子来说说分析ARM机器码的方法。
编译环境: 软件: keil5 ;交叉编译工具 arm-2011.09-70-arm-none-linux-gnueabi。
1: moveq r5,r1
0x00000018 01A05001 MOVEQ R5,R1
前者是汇编代码,后者是其对应的机器码。用第一个例子具体讲一下分析的方法。首先必须具备的一个手册就是ARM Architecture Reference Manual。这是学习ARM处理器绝对权威的一个参考资料。
在keil5中,ARM汇编手册在:点击菜单栏的 “Help” ->μVersion Help ->目录 ->Arm Compiler 5 User's Guides ->Assembler User Guide->ARM and Thumb Instructions
31--28是条件段,取值表如下所示:
0000(EQ) Z = 1 Equal 相等
0001(NE) Z = 0 Not Equal 不相等
0010(CS/HS) C = 1 Carry Set 有进位,无符号数 大于等于
0011(CC/LO) C = 0 Carry Clear 无进位,无符号数 小于
0100(MI) N = 1 Minus,negative 减 ,负数
0101(PL) N = 0 Plus,positive or zero 加, 正数或零
0110(VS) V = 1 Overflow Set 溢出
0111(VC) V = 0 Overflow Clear 无溢出
1000(HI) C ==1 and Z ==0 Unsighed Higher 无符号数大于
1001(LS) C ==0 or Z ==1 Unsigned lower or same 无符号数小于等于
1010(GE) N ==V Signed greater than or equal
1011(LT) N !=V Signed Less Than 有符号数小于
1100(GT) Z ==0 and N ==V Signed Greater Than 有符号数大于
1101(LE) Z ==1 or N!=V Signed less than or equal 有符号数小于等于
1110(none) 不启用条件判断
这里的例子中moveq后面跟的条件是相等,所以是0000(EQ),对应的机器码为0000
27--26为保留位,恒为00
25位:Distinguishes between the immediate and register forms of <shifter_operand>.
标志shifter_operand段存放的是立即数还是寄存器。若为寄存器则置零,若为立即数则置一。
24--21为opcode,标明指令的类型,下面是opcode的取值表
Opcode Mnemonic
0000 AND
0001 EOR
0010 SUB
0011 RSB
0100 ADD
0101 ADC
0110 SBC
0111 RSC
1000 TST
1001 TEQ
1010 CMP
1011 CMN
1100 ORR
1101 MOV
1110 BIC
1111 MVN
这个例子中mov对应的为1101
20位:Signifies that the instruction updates the condition codes.
表明该指令是否会影响程序状态字寄存器。是则置一,否则置零
19--16位:Specifies the first source operand register.
标明第一个源操作数寄存器,见每个指令的格式,有的有Rd,有的没有。
由MOV指令的一般格式可以看出,他是没有使用Rd的,所以这几位填全0,其他使用到Rn的,这几位填通用寄存器标号的二进制值。如r2,则为0010
15--12位:Specifies the destination register.
标明目的寄存器,填充方法同Rn
11--0位:Specifies the second source operand.
标明第二个源操作数,若为立即数则填该立即数的二进制值,若为通用寄存器则填通用寄存器标号的二进制值。
第一条指令的机器码到这里就分析完了,下面具其他几个不同的情况来验证上面的说法,分析方法还是一样的,这里就不一一分析了。
2、 movne r2,r1
0001 00 0 1101 0 0000 0010 000000000001
3、 cmp r1,r2
1110 00 0 1010 1 0001 0000 000000000010
4、 add r0,r0,r1
1110 00 0 0100 0 0000 0000 000000000001
5、 bic r0,r1,#0b101
1110 00 1 1110 0 0001 0000 000000000101
mov r13,#3 @把立即数3移动到r13寄存器中
mov r0,r1 @把寄存器r1移动到r0寄存器中
mov r0,r1,LSL#2 @r1逻辑左移2位,存入r0中
mov r0,r1,LSR#2 @r1逻辑右移2位,存入r0中
mrs r0,cpsr @cpsr寄存器的数据,存入r0中
msr cpsr,r0 @r0寄存器数据,存入cpsr中
立即数合法性:
4: mov r0,#256
0x0000001C E3A00C01 MOV R0,#0x00000100
MOV 操作的立即数最大值为十进制的256 ,十六进制的0x100,超过则会报错
如果想赋值超过256的立即数到寄存器中,则用下面的伪指令进行替换:
ldr r0,=0x12345678
and r0,r1,#0xFF @ r0 = r1&0xFF 按位与
orr r3,r0,#0x0F @ r3 = r0|0x0F 按位或
bic r0,r0,#0x03 @ 清除r0中的0号位和1号位 可简写成 bic r0,#0x03
tst r0,#0x20 @测试第6位是否为0 ,为0则Z标志置1
cmp r1,r0 @将R1与R0相减做比较,并根据结果设置CPSR的标志位 若r1>r0 C=1;若r1<r0 N=1;若r1=r0 Z=1(疑似同时C=1).
判断当前工作状态是否是ARM状态,是则切换到user 工作模式:
mrs r0,cpsr
tst r0,#0x20
andeq r0,r0,#0xFFFFFFE0
orreq r0,r0,#0x10
msreq cpsr,r0
使能中断和快速中断?
mrs r0,cpsr
bic r0,r0,#0xc0
msr cpsr,r0
add r0,r1,r2 @r0=r1+r2
sub r0,r1,#3 @r0= r1 - 3
sub r0,r1,r2,LSL#1
mul r1,r2,r3 @r1=r2*r3
EQ=equal(相等)
NE=not equal(不等)
LT=little than(小于)
LE=little and equal(小于等于)
GT=great than(大于)
GE=great and equal(大于等于)
b main //跳转到标号为main地代码处
bl func //保存下一条要执行的指令的位置到 LR寄存器,跳转函数func
//当跳转代码结束后,用MOV PC,LR指令跳回来
beq addr //当CPSR寄存器中的Z条件码置位时,跳转到该地址处
bne addr //当不等时,跳转到地址addr
例:用汇编实现下面功能
void main(void) { int ret=0; func1(2); while(1) {}; } func1(int a) { if(a==2) return func2(a); else return func3(a); } func2(int a) { return a+3; } func3(int a) { return a-1; }
版本v1
.text
mov r3,#0
mov r0,#2
b func1
mainend: b mainend
func1:
cmp r0,#2
beq func2
bne func3
loop:
b mainend
func2:
add r0,r0,#2
b loop
func3:
sub r0,r0,#1
b loop
.end
版本v2
.text
mov r3,#0
mov r0,#2
bl func1
mainend: b mainend
func1:
mov r8,LR
cmp r0,#2
bleq func2
blne func3
mov LR,r8
mov PC,LR
func2:
add r0,r0,#2
mov PC,LR
func3:
sub r0,r0,#1
mov PC,LR
.end
例:实现 延时1秒函数
@delay fos 1 second
delay1s:
ldr r4,=0x3FFFF //估值
loop_delay1s:
sub r4,r4,#1
cmp r4,#0
bne loop_delay1s
delay1s_end:
mov pc,lr
注:load/store 架构规定,存储器之间不能直接拷贝,需通过寄存器做中转
ldr r0,[r1] (load) //r0=*r1 r1里存放的是地址,把该地址里存放的内容读入到r0中
//LDRB(byte) LDRH(half word)
ldr r0,[r1,#8] //r0=*(r1+8) 存储器地址为r1+8的字数据读入寄存器0。
ldr r0,[r1,#8]! //r0=*(r1+8) 存储器地址为r1+8的字数据读入寄存器0,并更新r1寄存器,r1 = r1+8。
ldr pc,_irq // pc = *(_irq) 将标号中的内容放入pc中
str r0,[r1] (store) // *r1 = r0 将寄存器r0中值写入到存储器地址为r1的空间中
str r0,[r1],#4 // r0=*r1, r1=r1+4 将r0 中的字数据写入以r1为地址的内存中,并将新地址r1+4 写入r1
str r0,[r1,#4] //*(r1+4)=r0 将r0 中的字数据写入以r1+4 为地址的内存中
str r0,[r1,#4]! //*(r1+4)=r0 将r0 中的字数据写入以r1+4 为地址的内存中,并将新地址r1+4 写入r1
例:拷贝srcBuf里内容 到destBuf中
.text
mov r0 ,#0
ldr r1,=srcBuf
ldr r3,=destBuf
loop:
ldrb r2,[r1],#1
strb r2,[r3],#1
cmp r0,#3
add r0,#1
bne loop
.data
srcBuf:
.byte 0x1,0x2,0x3,0x4
destBuf:
.space 8
.end
例:拷贝字符串srcBuf里内容 到destBuf中
.text
mov r0 ,#0
ldr r1,=srcBuf
ldr r3,=destBuf
loop:
ldrb r2,[r1],#1
strb r2,[r3],#1
cmp r0,#7
add r0,r0,#1
bne loop
.data
srcBuf:
.string "abcdefg\0"
destBuf:
.space 8
.end
批量操作指令:
ia-Increment After 赋值之后再增加
ib-Increment Before 增加之后再赋值
da-Dec After 赋值之后再减少
db-Dec Before 减少之后再赋值
ldmia r0!, {r3 - r10} //r0里地址指向的内容批量,load 到r3~r10寄存器中, r0里地址会自动加4
stmia r0!, {r3 - r10} //把r3~r10寄存器中内容,store 到r0里地址执行空间中,r0里地址会自动加4
例:实现块数据批量拷贝
.text
ldr r12,=srcBuf
ldr r13,=destBuf
ldmia r12!,{r0 - r11}
stmia r13!,{r0 - r11}
.data
srcBuf:
.string "abcd1234abcd1234abcd1234abcd1234abcd1234abcd123\0"
destBuf:
.space 12*4
.end
stmfd sp!,{r0-r12,lr} //将寄存器r0~r12 lr中的值存入栈中,常用于中断保护现场,“!”表示会自动偏移
ldmfd sp!,{r0-r12,pc}^ //将栈中值逐个弹出到寄存器r0~r12 pc中,常用于中恢复断现场,“^”表示会恢复spsr到cpsr
swi 0x02 @产生软中断, 软中断号为2