14.2 ARM
ARM处理器,就像其他的“纯”RISC处理器一样,缺少除法指令,缺少32位常数乘法的单条指令。利用一个技巧,通过加法,减法,移位是可以实现除法的。 这里有一个32位数被10(20,3.3常量除法)除的例子,输出商和余数。
; takes argument in a1
; returns quotient in a1, remainder in a2
; cycles could be saved if only divide or remainder is required
SUB a2, a1, #10 ; keep (x-10) for later
SUB a1, a1, a1, lsr #2
ADD a1, a1, a1, lsr #4
ADD a1, a1, a1, lsr #8
ADD a1, a1, a1, lsr #16
MOV a1, a1, lsr #3
ADD a3, a1, a1, asl #2
SUBS a2, a2, a3, asl #1 ; calc (x-10) - (x/10)*10
ADDPL a1, a1, #1 ; fix-up quotient
ADDMI a2, a2, #10 ; fix-up remainder
MOV pc, lr
14.2.1 Xcode优化模式(LLVM)+ARM模式
__text:00002C58 39 1E 08 E3 E3 18 43 E3 MOV R1, 0x38E38E39
__text:00002C60 10 F1 50 E7 SMMUL R0, R0, R1
__text:00002C64 C0 10 A0 E1 MOV R1, R0,ASR#1
__text:00002C68 A0 0F 81 E0 ADD R0, R1, R0,LSR#31
__text:00002C6C 1E FF 2F E1 BX LR
运行原理
这里的代码和优化模式的MSVC和GCC生成的基本相同。显然,LLVM在产生常量上使用相同的算法。
善于观察的读者可能会问,MOV指令是如何将32位数值写入寄存器中的,因为这在ARM模式下是不可能的。实际上是可能的,但是,就像我们看到的,与标准指令每条有四个字节不同的是,这里的每条指令有8个字节,其实这是两条指令。第一条指令将值0x8E39装入寄存器的低十六位,第二条指令是MOVT,它将0x383E装入寄存器的高16位。IDA知道这些顺序,并且为了精简紧凑,将它精简转换成一条伪代码。
SMMUL (Signed Most Significant Word Multiply)实现两个32位有符号数的乘法,并且将高32位的部分放在r0中,弃掉结果的低32位部分。
MOV R1,R0,ASR#1 指令算数右移一位。
ADD R0,R1,LSR#31 R0=R1+R0>>32
事实上,在ARM模式下,并没有单独的移位指令。相反,像(MOV,ADD,SUB,RSB)3 这样的数据处理指令,第二个操作数需要被移位。ASR表示算数右移,LSR表示逻辑右移。
14.2.2 优化 Xcode(LLVM)+thumb-2 模式
MOV R1, 0x38E38E39
SMMUL.W R0, R0, R1
ASRS R1, R0, #1
ADD.W R0, R1, R0,LSR#31
BX LR
在thumb模式下有些单独的移位指令,这个例子中使用了ASRS(算数右移)
14.2.3 Xcode非优化模式(LLVM) keil模式
非优化模式 LLVM不生成我们之前看到的那样的代码,它插入了一个调用库函数的call __divsi3
关于keil:通常插入一个调用库函数的call __aeabi_idivmod
当前内容版权归 Dennis Yurichev 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 Dennis Yurichev .